【题解】P1203 坏掉的项链
题面
前言
其实这个题 $ n \le 350 $ 给的还是太保守了
毕竟 $ O(n) $ 的算法(算上破环为链的常数 2 也没多少)
正文
环状结构是我们不熟悉的
经典 trick:破环为链
考虑倍长整个序列,举个例子(样例):
wwwbbrwrbrbrrbrbrwrwwrbwrwrrb → wwwbbrwrbrbrrbrbrwrwwrbwrwrrb wwwbbrwrbrbrrbrbrwrwwrbwrwrrb
这样环上的每一个区间都可以在倍长序列上找到
其次,想一下暴力是怎么做的,枚举断点 $ i $,分别扫描 $ i $ 右方以及 $ i-1 $ 左方的相同颜色块,嗯——这是个 $ O(n^2) $ 的做法(其实能通过)
贴个暴力代码:
点击查看代码
#include<iostream>
#include<cstring>
using namespace std;
string s;
inline int f(int x){
int res=0;
char l=s[x],r=s[x+1];
for(int i=x;true;i--){
if(s[i]==l||s[i]=='w'){
res++;
}else{
break;
}
}
for(int i=x+1;true;i++){
if(s[i]==r||s[i]=='w'){
res++;
}else{
break;
}
}
return res;
}
int main(){
int ans,n;
ans=-1;
cin>>n;cin>>s;
s=s+s+s;
for(int i=n;i<2*n;i++){//三段 从中间那一段开始处理
if(s[i]==s[i+1]){
continue;
}
if(s[i]=='w'){
s[i]='r';
ans=max(ans,f(i));
s[i]='b';
ans=max(ans,f(i));
s[i]='w';
}
ans=max(ans,f(i));
}
ans=min(ans,n);//最长也不能比总长长
if(ans==-1){
ans=n;//出现这种情况必定是一路continue过来的
}
cout<<ans<<endl;
return 0;
}
好,我们人为规定一下 $ n\le 10^5 $
注意到对于每个断点 $ i $,枚举相同颜色块长度的操作大大降低了程序运行的效率
考虑优化——可以类比前缀和
我们分别用 rRed,rBlue,lRed,lBlue 数组记录对于每个点 $ i $ 向右红,向右蓝,向左红,向左蓝最长的延伸距离,而这四个数组都可以在 $ O(n) $ 的复杂度预处理出来。最后直接枚举断点 $ i $,在这些数组中取最大值即可!
贴个预处理优化后的代码(来自 luogu 题解区):
点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=350+10;
int n,rR[maxn<<1],rB[maxn<<1],lR[maxn<<1],lB[maxn<<1],ans;
char s[maxn<<1];
int main(){
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++){
s[i+n]=s[i];
}
lB[0]=0;
lR[0]=0;
for(int i=1;i<=n*2;i++){
if(s[i]=='w'){
lR[i]=lR[i-1]+1;
lB[i]=lB[i-1]+1;
}else if(s[i]=='r'){
lR[i]=lR[i-1]+1;
}else if(s[i]=='b'){
lB[i]=lB[i-1]+1;
}
}
for(int i=n*2;i>=1;i--){
if(s[i]=='w'){
rR[i]=rR[i+1]+1;
rB[i]=rB[i+1]+1;
}else if(s[i]=='r'){
rR[i]=rR[i+1]+1;
}else if(s[i]=='b'){
rB[i]=rB[i+1]+1;
}
}
for(int i=(n<<1)-1;i>=1;i--){
ans=max(ans,max(lR[i],lB[i])+max(rR[i+1],rB[i+1]));
}
ans=min(ans,n);
printf("%d\n",ans);
return 0;
}
嗯?但是,云落并不满足于这样的时空复杂度(在被无数次卡常的经历毒打之后,云落努力追求彻底的优化)
注意力惊人的云落发现扫描的过程是相类似的,并且上述四个数组在断点 $ i $ 处的数据是可以由之前推理得出的,详见代码吧!
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=350+10;
int n;
char s[maxn<<1];
int main(){
//输入
scanf("%d%s",&n,s);
//Trick:破环为链
memcpy(s+n,s,n);
//cntw 记录扫描到位置 i 时 w 的长度
//r 表示 i 后面(含 i)所有的与 s[i] 颜色相同的色块长度
//l 表示 i 前面(不含 i)所有的与 s[i-1] 颜色相同的色块长度
int l=0,r=0,cntw=0;
//表示目前统计的颜色是 c
char c;
//记录答案捏!
int ans=0;
for(int i=0;i<(n<<1);i++){
if(s[i]=='w'){
r++;
cntw++;//又加入一个 w
}else if(s[i]==c){
r++;
cntw=0;//w 段被断开
}else{
ans=max(ans,l+r);//i 是断点,取 l+r 的最大值
l=r-cntw;
r=cntw+1;
cntw=0;//w 段被断开
c=s[i];
}
}
ans=max(ans,l+r);
//输出
printf("%d\n",min(ans,n));
return 0;
}
后记
重要的优化思想,虽然是个橙题,但出的很好捏!
P.S. 本蒟蒻笔名云落

浙公网安备 33010602011771号