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

OJ: luogu

题目 ID: P1203

标签: 模拟环形处理USACO

日期: 2026-05-31 16:58

题目解析

题意简述

有一个环形项链,由 r(红)、b(蓝)、w(白)三种颜色的珠子串成。

你可以选择一个位置断开项链,然后从断点开始,同时向左右两个方向收集珠子。

收集规则:

  • 从断口出发,顺着一个方向走,遇到的珠子只要和第一个珠子颜色相同,就可以收集。
  • 白色珠子 w 很特殊——它可以假装成红色或蓝色。也就是说,遇到白色时,它永远可以被收集;并且如果是第一个珠子是白色,它的颜色由后面遇到的第一个非白色珠子决定。
  • 遇到第一个不能匹配的颜色就停止。

问:选择一个最优的断开位置,最多能收集多少颗珠子?

样例:wwwbbrwrbrbrrbrbrwrwwrbwrwrrb(n=29)→ 答案 11

思路分析

这题本质上是个模拟题,主要有三个需要处理的地方:

1. 环形处理

项链是环形的,但我们存储时用一个一维数组 a[1..n] 就能存下。

访问时用两个辅助函数绕圈:

  • 顺时针(下标加1)i++,如果超过 n 就回到 1
  • 逆时针(下标减1)i--,如果小于 1 就回到 n

这样就实现了在数组上"走圈"的效果,不需要真的把数组翻倍。

2. 白色珠子的处理

白色 w 可以当任何颜色,所以:

  • 如果在收集过程中遇到 w,它总是能收集的,不需要和基准颜色比较。
  • 如果当前是从白色开始的(即第一个珠子是 w),那么基准颜色还不确定,继续往前走,直到遇到一个非白色珠子,就把基准颜色设定为它。

代码中体现为:

cpp
if (color == 'w' && a[s] != 'w')
    color = a[s];

3. 避免重复计数

从断点向两个方向收集时,有可能两边收集到同一个珠子(如果整条项链全是白色,或者两边的颜色恰好相通)。

解决方案:

  • 先做顺时针收集,记录下顺时针能走到的最远位置 endpos
  • 再做逆时针收集时,强制要求不能超过 endpos,这样两者就不会重叠。

算法流程

对每个可能的断点 i(即从珠子 i 开始顺时针,从珠子 i-1 开始逆时针)执行:

  1. collect_clockwise(i, endpos):从 i 开始顺时针走,按规则收集珠子,把最后能到达的位置赋给 endpos
  2. collect_anti_clockwise(i-1, endpos):从逆时针方向收集,但遇到 endpos 就停,不走重叠。
  3. 两者相加,就是在这个位置断开的收集总数。
  4. 取所有位置中的最大值输出。

代码解读

  • idx_next(i) / idx_pre(i):在环形数组上前进/后退一步。
  • collect_clockwise(i, endpos):从 i 顺时针出发,遇到同样颜色或白色就继续,否则停下。color 变量记录基准颜色,初始为 a[i]
  • collect_anti_clockwise(i, endpos):类似地逆时针走,多了一个限制——不能走到 endpos,防止重叠。
  • 主循环:枚举每个 i,计算两边收集总数,更新答案。

难点和易错点

说明
白色开头 如果第一个珠子是 w,基准颜色由后面第一个非 w 决定,不要上来就认为 color = 'w'
重复收集 不加 endpos 限制的话,两边可能收集到同一个珠子,答案偏大
全白项链 全是 w 时要能正确收集所有珠子,答案应为 n
环形边界 下标加减时要考虑绕回,否则数组越界或答案错误

总结

这道题的关键在于:

  1. 取模思维(手动判断边界)处理环形结构
  2. 白色珠子的变色特性本质上是"它可以无条件被收集",只是会推迟基准颜色的确定
  3. endpos 标记 来防止两个方向重复计数

整体思路简单直接,就是模拟各个断点的情况取最大值,复杂度 O(n2)O(n^2),对 n350n \le 350 的数据绰绰有余。

代码

cpp
/*-----------------
* author: Rainboy 🤠 /📬 : rainboylvx@qq.com ⌚: 2023-07-17 11:29:16
*----------------*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+5,maxe = 1e6+5; //点与边的数量

//////// 常用宏
#define F(n) for(int i=1;i<=n;++i)
#define FF(i,n) for(int i=1;i<=n;++i)
#define F3(i,s,n) for(int i=s;i<=n;++i)

#ifdef DEBUG
#include "utils/clock.hpp"
#include "utils/log.hpp"
#else
#define log(...)
#endif


int n,m;
/* 定义全局变量 */

char a[maxn]; //常用的一个数组 

void init()
{
    /* cin >> n >> m; */
    scanf("%d",&n);
    scanf("%s",a+1);
}

//i的下一个下标
int idx_next(int i) {
    ++i;
    if( i > n) i=1;
    return i;
}

int idx_pre(int i) {
    --i;
    if( i <=0) i = n;
    return i;
}

//从i开始的顺时针 收集的数目
int collect_clockwise(int i,int & endpos) {
    char color = a[i]; // 在第i个位置的颜色
    int cnt = 1;
    for(int s = idx_next(i); s != i ; s = idx_next(s)) {
        if( color == 'w' && a[s] != 'w') //如果你是从白色点开始
            color = a[s];               // 那么第一个你遇到的非白色点就是第一个白色点应该的颜色
        if( a[s] == color || a[s] == 'w'){
            ++cnt;
            endpos = s;
        }
        else break;
    }
    return cnt;
}

//从i开始的逆时针 收集的数目,但不能达到enpos 这个位置
int collect_anti_clockwise(int i,int endpos) {
    if( i == endpos)
        return 0;
    char color = a[i]; // 在第i个位置的颜色
    int cnt = 1;
    for(int s = idx_pre(i) ; s != i && s != endpos ; s= idx_pre(s))
    {
        if( color == 'w' && a[s] != 'w')
            color = a[s];
        if( a[s] == color || a[s] == 'w'){
            ++cnt;
        }
        else break;
    }
    return cnt;
}

int main(int argc,char * argv[]){
    cin.sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
#ifdef DEBUG
    Clock t("main time");
#endif
    init();
    int ans = 0;
    F(n) {
        int endpos;
        int t = collect_clockwise(i,endpos);
        t += collect_anti_clockwise(idx_pre(i),endpos);
        if( ans < t)
            ans=t;
    }
    printf("%d\n",ans);
    
    return 0;
}