【题解】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. 本蒟蒻笔名云落

posted @ 2024-11-18 16:08  sunxuhetai  阅读(25)  评论(0)    收藏  举报