记录84
cpp
#include<bits/stdc++.h>
using namespace std;// 典型的序列决策
int leftR[800],leftB[800],rightR[800],rightB[800];
int main(){//从左右两头数红蓝两种状态,四种组合
int n,ans=0;
string s,s2;
cin>>n>>s;
s2=s+s;// 环状,加一个自己就能包括收尾相连的状态了
int len=2*n;
if(s2[0]=='r'||s2[0]=='w') leftR[0]=1;
if(s2[0]=='b'||s2[0]=='w') leftB[0]=1;
for(int i=1;i<len;i++){//左边从开头第二个数状态的情况
if(s2[i]=='r'||s2[i]=='w') leftR[i]=leftR[i-1]+1;
if(s2[i]=='b'||s2[i]=='w') leftB[i]=leftB[i-1]+1;
}
if(s2[len-1]=='r'||s2[len-1]=='w') rightR[len-1]=1;
if(s2[len-1]=='b'||s2[len-1]=='w') rightB[len-1]=1;
for(int i=len-2;i>=0;i--){//右边到数第二个数状态
if(s2[i]=='r'||s2[i]=='w') rightR[i]=rightR[i+1]+1;
if(s2[i]=='b'||s2[i]=='w') rightB[i]=rightB[i+1]+1;
}
for(int i=0;i<=len-2;i++){
ans=max(ans,max(leftR[i],leftB[i])+max(rightR[i+1],rightB[i+1]));
}//状态转移,每次取左边最大的跟右边最大的,然后将答案更新为最大
if(ans>n) ans=n;//最大不会超珠子数
cout<<ans;
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P1203
思路
🎯 问题本质
- 有一条环形项链 (首尾相连),由
n颗珠子组成,颜色为'r'(红)、'b'(蓝)、'w'(白) - 白色珠子可以当作任意颜色
- 你可以在任意两个相邻珠子之间"打断",将环变成一条线
- 然后从左端向右 收集同色珠子(直到遇到不同颜色),从右端向左也做同样操作
- 目标:最大化总共能收集的珠子数
💡 关键点:
- 打断位置决定左右两段
- 左右可以是不同颜色(例如左边收红色,右边收蓝色)
- 白色可灵活匹配
🧠 解题策略:破环成链 + 预处理左右最长同色段
1. 破环成链
- 将原字符串
s复制一份拼接:s2 = s + s - 这样任意长度为
n的连续子串都对应环上的一种打断方式
2. 预处理四个数组
对 s2(长度 2n)预处理:
| 数组 | 含义 |
|---|---|
leftR[i] |
从 s2[0] 到 s2[i],以红色(或白)结尾的最长连续非蓝段长度(即:若只允许收红/白,从左到 i 能收多少) |
leftB[i] |
类似,只允许收蓝/白 |
rightR[i] |
从 s2[i] 到 s2[2n-1],向右收红/白的最大长度 |
rightB[i] |
向右收蓝/白的最大长度 |
✅ 注意:因为白色可当任意色,所以:
- 在"收红色"时,
'w'可以算作'r'- 在"收蓝色"时,
'w'可以算作'b'
3. 枚举所有打断位置
- 在
s2中,打断位置对应于 在 i 和 i+1 之间断开 (i从 0 到2n-2) - 左半部分:
s2[0..i]→ 最多可收max(leftR[i], leftB[i]) - 右半部分:
s2[i+1..2n-1]→ 最多可收max(rightR[i+1], rightB[i+1]) - 总数 = 左 + 右
4. 边界处理
- 最多不能超过
n(总珠子数)
代码分析
cpp
#include<bits/stdc++.h>
using namespace std;
int leftR[800], leftB[800], rightR[800], rightB[800];
- 定义四个预处理数组(大小 800 > 2×350,安全)
cpp
int main(){
int n, ans = 0;
string s, s2;
cin >> n >> s;
s2 = s + s; // 破环成链
int len = 2 * n;
🔹 初始化 left 数组(从左往右)
cpp
if(s2[0] == 'r' || s2[0] == 'w') leftR[0] = 1;
if(s2[0] == 'b' || s2[0] == 'w') leftB[0] = 1;
- 第一个字符:
- 如果是
'r'或'w',可以作为红色段开头 →leftR[0]=1 - 如果是
'b'或'w',可以作为蓝色段开头 →leftB[0]=1
- 如果是
cpp
for(int i = 1; i < len; i++){
if(s2[i] == 'r' || s2[i] == 'w')
leftR[i] = leftR[i-1] + 1;
if(s2[i] == 'b' || s2[i] == 'w')
leftB[i] = leftB[i-1] + 1;
}
- 若当前是
'r'/'w',则红色段延续;否则leftR[i]保持 0(未显式清零,但全局初始化为 0) - 同理处理蓝色
⚠️ 注意:如果
s2[i] == 'b',则leftR[i]不更新(仍为 0),表示不能继续收红色
🔹 初始化 right 数组(从右往左)
cpp
if(s2[len-1] == 'r' || s2[len-1] == 'w') rightR[len-1] = 1;
if(s2[len-1] == 'b' || s2[len-1] == 'w') rightB[len-1] = 1;
- 最后一个字符初始化
cpp
for(int i = len - 2; i >= 0; i--){
if(s2[i] == 'r' || s2[i] == 'w')
rightR[i] = rightR[i+1] + 1;
if(s2[i] == 'b' || s2[i] == 'w')
rightB[i] = rightB[i+1] + 1;
}
- 从右往左累加,逻辑同 left
🔥 枚举所有打断位置
cpp
for(int i = 0; i <= len - 2; i++){
ans = max(ans,
max(leftR[i], leftB[i]) + max(rightR[i+1], rightB[i+1])
);
}
- 在
i和i+1之间打断 - 左边
[0..i]:最多收max(leftR[i], leftB[i]) - 右边
[i+1..end]:最多收max(rightR[i+1], rightB[i+1]) - 更新全局最大值
🔚 边界修正
cpp
if(ans > n) ans = n;
cout << ans;
return 0;
}
- 因为项链只有
n颗珠子,不可能收集超过n颗 - 某些情况下(如全白),计算结果可能为
2n,需截断
✅ 例如:
n=3,s="www"
leftR[2]=3,rightR[3..]不存在,但在s2="wwwwww"中,i=2时:
leftR[2]=3,rightR[3]=3→ 总和 6,但实际最多只能拿 3 颗 → 修正为n
总结
| 要点 | 说明 |
|---|---|
| 核心技巧 | 破环成链(s+s) |
| 状态设计 | 四个方向数组,分别记录红/蓝(含白)的最长连续段 |
| 白色处理 | 在红/蓝统计中都包含 'w' |
| 枚举打断 | 遍历所有 i,取左右最大值之和 |
| 边界修正 | 结果不超过 n |