Cycle Correspondence

USACO Silver 2023 December

算法讲解与题解

Key Concepts: Circular Arrays, Relative Positioning, Frequency Arrays

1. 题目理解

  • 输入:两个长度为 $K$ 的序列 $A$ 和 $B$(包含 $1 \dots N$ 中的不同数字)。
  • 关系:这两个序列代表两个“环”。
  • 目标:
    1. 可以将 $B$ 任意旋转
    2. 可以先将 $B$ 翻转,再旋转。
    3. 求最大匹配数(重合点的数量)。

2. 解题思路分解

总匹配分数可以分为两部分计算:

\[ \text{Total Answer} = \text{Score}_{out} + \text{Score}_{in} \]
  • $Score_{out}$ (环外的点):
    既不在 $A$ 也不在 $B$ 中的点。这些点无论如何旋转,因为都不在环上,所以如果不考虑位置(题目暗示没出现的点默认配对),或者理解为它们是背景,这部分是固定的常数。
    简单统计即可。
  • $Score_{in}$ (环内的点):
    需要找到最佳的旋转角度,使得 $A$ 和 $B$ 中相同数字位置重合最多。

3. 环内匹配算法:相对位置法

暴力枚举所有 $K$ 个旋转需要 $O(K^2)$,太慢。我们需要 $O(K)$。

关键洞察: 如果数字 $X$ 在旋转 $R$ 后匹配,那么它们的原始索引差 $Index_B(X) - Index_A(X)$ 必然与 $R$ 有关。

A (Indices): 1 2 3 4 pos=1 pos=2 pos=3 pos=4 B (Values): 3 4 1 2 i=1 i=2 i=3 i=4 计算偏移量 (Diff): Val 3: B_idx(1) - A_idx(3) = -2 (+K) = 2 Val 4: B_idx(2) - A_idx(4) = -2 (+K) = 2 Val 1: B_idx(3) - A_idx(1) = 2 } Mode = 2

统计每个位置差出现的频率 (Frequency Map),频率最高的就是最佳旋转。

4. 具体实现步骤

  1. 记录位置: 建立数组 `pos[x]` 记录数字 `x` 在序列 A 中的位置。
  2. 遍历 B: 对于序列 B 中的每个数字 `val = b[i]`:
    • 如果 `val` 也在 A 中(`pos[val]` 存在):
    • 计算偏移 `d = i - pos[val]`。
    • 处理环状边界:如果 `d < 0`,则 `d += K`。
  3. 桶计数: `count[d]++`。
  4. 取最大值: `max(count)` 即为当前方向的最大匹配数。
  5. 翻转处理: 将数组 B `reverse`,重复上述过程,取两者的较大值。

5. 完整代码 (C++)


#include 
using namespace std;
typedef long long ll;

int n, k;
int a[500005], b[500005], seen[500005];
int pos[500005];    // 1-based index storage for Array A
int ans;            // Count of items outside both cycles

// Core Logic: Calculate max matches for fixed orientation
int solve() {
    vector v;
    v.resize(k+1, 0); // Frequency bucket for shifts
    for (int i = 1; i <= k; i++) {
        if (pos[b[i]] == 0) continue; // Item in B not found in A
        int d = i - pos[b[i]];        // Calculate shift difference
        if (d < 0) d += k;            // Handle circular wrap-around
        v[d]++;
    }
    // Return the shift with the highest frequency
    return *max_element(v.begin(), v.end());
}

int main() {
    scanf("%d %d", &n, &k);
    
    // Read A and mark seen
    for (int i = 1; i <= k; i++) {
        scanf("%d", &a[i]);
        seen[a[i]] = 1;
    }
    // Read B and mark seen
    for (int i = 1; i <= k; i++) {
        scanf("%d", &b[i]);
        seen[b[i]] = 1;
    }

    // Part 1: Count items NOT in either cycle (Always valid)
    for (int i = 1; i <= n; i++)
        if (!seen[i]) ans++;

    // Precompute positions of elements in A for O(1) lookup
    for (int i = 1; i <= k; i++) 
        pos[a[i]] = i;
    
    // Part 2: Solve for original direction
    int ans0 = solve();
    
    // Part 3: Solve for reversed direction
    reverse(b+1, b+k+1);
    int ans1 = solve();

    // Final Answer = Outside + Max(Inside_Original, Inside_Reversed)
    printf("%d\n", ans + max(ans0, ans1));

    return 0;
}
                

6. 复杂度分析

  • 时间复杂度:
    • 读取输入和标记 `seen`:$O(K)$
    • 计算环外点:$O(N)$
    • 建立位置映射:$O(K)$
    • `solve()` 函数遍历 B 并更新桶:$O(K)$
    • 寻找桶中最大值:$O(K)$
    • 总计:$O(N + K)$。由于 $N, K \le 5 \times 10^5$,这可以通过测试。
  • 空间复杂度:
    • 数组存储:$O(N)$。

总结

这个问题的核心是将“旋转匹配”问题转化为“统计相对位置差频率”的问题。

这种技巧在处理环形数组或周期性字符串匹配问题时非常常用。