题目解析
题意简述
有一个环形项链,由 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 开始逆时针)执行:
collect_clockwise(i, endpos):从i开始顺时针走,按规则收集珠子,把最后能到达的位置赋给endpos。collect_anti_clockwise(i-1, endpos):从逆时针方向收集,但遇到endpos就停,不走重叠。- 两者相加,就是在这个位置断开的收集总数。
- 取所有位置中的最大值输出。
代码解读
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 |
| 环形边界 | 下标加减时要考虑绕回,否则数组越界或答案错误 |
总结
这道题的关键在于:
- 用取模思维(手动判断边界)处理环形结构
- 白色珠子的变色特性本质上是"它可以无条件被收集",只是会推迟基准颜色的确定
- 用
endpos标记 来防止两个方向重复计数
整体思路简单直接,就是模拟各个断点的情况取最大值,复杂度
代码
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;
}