[IOI 1993 / USACO1.1] 坏掉的项链 Broken Necklace

GitHub跳转原题关系图返回列表

枚举每个断点,分别向左右按颜色和白珠规则模拟收集,并用终点位置避免两边重复计数。

OJ: luogu

题目 ID: P1203

难度:普及-

标签:模拟环形处理USACO

日期: 2026-05-31 16:58

题意

给出一条由 rbw 组成的环形项链。

你可以任选一个位置断开,然后从断口向左右两个方向收集珠子。

每个方向都只能继续收集:

  • 白色珠子 w
  • 和当前认定颜色相同的珠子

问最多能收集多少颗。

思路

n 很小,直接枚举断点模拟就够了。

最直接的教学版写法如下:

cpp
// brute.cpp:枚举断点并直接按规则双向收集珠子,作为教学版和对拍基准程序。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 405;

int n;
char a[MAXN];

int next_idx(int x) {
    x++;
    if (x > n) {
        x = 1;
    }
    return x;
}

int prev_idx(int x) {
    x--;
    if (x <= 0) {
        x = n;
    }
    return x;
}

int collect_clockwise(int x, int &end_pos) {
    char color = a[x];
    int cnt = 1;
    end_pos = x;

    for (int p = next_idx(x); p != x; p = next_idx(p)) {
        if (color == 'w' && a[p] != 'w') {
            color = a[p];
        }
        if (a[p] == color || a[p] == 'w') {
            cnt++;
            end_pos = p;
        }
        else {
            break;
        }
    }

    return cnt;
}

int collect_counterclockwise(int x, int end_pos) {
    if (x == end_pos) {
        return 0;
    }

    char color = a[x];
    int cnt = 1;

    for (int p = prev_idx(x); p != x && p != end_pos; p = prev_idx(p)) {
        if (color == 'w' && a[p] != 'w') {
            color = a[p];
        }
        if (a[p] == color || a[p] == 'w') {
            cnt++;
        }
        else {
            break;
        }
    }

    return cnt;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    cin >> (a + 1);

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        int end_pos = 0;
        int total = collect_clockwise(i, end_pos);
        total += collect_counterclockwise(prev_idx(i), end_pos);
        ans = max(ans, total);
    }

    if (ans > n) {
        ans = n;
    }
    cout << ans << '\n';
    return 0;
}

对于每个断点:

  1. 从断口右边顺时针收集;
  2. 从断口左边逆时针收集;
  3. 两边数量相加更新答案。

实现时要注意三件事:

核心公式

枚举断点 cutcut。设从断口顺时针最多能收集 R(cut)R(cut) 颗,逆时针且不与右侧重复时最多能收集 L(cut)L(cut) 颗,则这个断点的贡献是:

ans(cut)=min{n, R(cut)+L(cut)} ans(cut)=\min\{n,\ R(cut)+L(cut)\}

最终答案为:

max0cut<nans(cut) \max_{0\leqslant cut<n} ans(cut)
  1. 项链是环形,下标要能绕回;
  2. 白色 w 可以当任意颜色;
  3. 两边不能重复收集同一颗珠子,所以顺时针先记录终点,逆时针不能越过它。

公式解释:每个断点都独立计算一次左右两边最多能拿多少。两边相加可能超过整条项链长度,所以要和 n 取最小值;最后再对所有断点取最大值,就是最优断开位置。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 405;

int n;
char a[MAXN];

int next_idx(int x) {
    x++;
    if (x > n) {
        x = 1;
    }
    return x;
}

int prev_idx(int x) {
    x--;
    if (x <= 0) {
        x = n;
    }
    return x;
}

// 从 x 开始顺时针收集,并记录最后一个被收集到的位置。
int collect_clockwise(int x, int &end_pos) {
    char color = a[x];
    int cnt = 1;
    end_pos = x;

    for (int p = next_idx(x); p != x; p = next_idx(p)) {
        if (color == 'w' && a[p] != 'w') {
            color = a[p];
        }
        if (a[p] == color || a[p] == 'w') {
            cnt++;
            end_pos = p;
        }
        else {
            break;
        }
    }

    return cnt;
}

// 从 x 开始逆时针收集,但不能和顺时针部分重叠到 end_pos。
int collect_counterclockwise(int x, int end_pos) {
    if (x == end_pos) {
        return 0;
    }

    char color = a[x];
    int cnt = 1;

    for (int p = prev_idx(x); p != x && p != end_pos; p = prev_idx(p)) {
        if (color == 'w' && a[p] != 'w') {
            color = a[p];
        }
        if (a[p] == color || a[p] == 'w') {
            cnt++;
        }
        else {
            break;
        }
    }

    return cnt;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    cin >> (a + 1);

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        int end_pos = 0;
        int total = collect_clockwise(i, end_pos);
        total += collect_counterclockwise(prev_idx(i), end_pos);
        ans = max(ans, total);
    }

    if (ans > n) {
        ans = n;
    }
    cout << ans << '\n';
    return 0;
}

复杂度

  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度:O(n)O(n)

总结

这题是典型的环形模拟题。

把环形下标、白珠规则和“不重复计数”这三件事处理好,代码就很稳定。