题解:luogu P10069([CCO 2023] Flip it and Stick it)

1. Description

给定两个 01 串 S 和 T,每一次操作可以选择 S 的一段子串翻转,求最少需要多少次操作使得 S 中不包含 T,或者返回无解。

2. Solution

发现 \(|T|\le 3\),所以我们果断选择分类讨论 \(|T|=1,2,3\) 时的情况。
第一种,\(|T|=1\),最简单的一种情况,如果 \(S\) 中含有 \(T\),输出 \(-1\),否则输出 \(1\)
第二种,\(|T|=2\),根据题目给出的部分分,我们分为 \(T_1=T_2,T_1\ne T_2\) 继续讨论。
\(T_1=T_2\),不妨令 \(T=00\),反之可以通过 \(0\)\(1\)\(1\)\(0\) 的操作化成这种情况。
发现合法的串一定满足相邻的两个 \(1\) 之间的 \(0\) 的个数不超过 \(1\) 个,因此 \(0\) 至多比 \(1\)\(1\) 个,否则无解,反之一定有解。
考虑构造最少次数,如果存在连续的,大于等于 \(2\)\(0\),我们操作一次可以将 \(1\)\(0\) 移动到两个连续的 \(1\) 之间,也就是要操作 \(\sum x_i-1\) 次,其中 \(x_i\) 表示每一个连续 \(0\) 段中 \(0\) 的数量,等价于 \(00\) 出现的次数。
然后考虑 \(T_1\ne T_2\),不妨令 \(T_1=1,T_2=0\),另一种情况同理。
不难发现最后得到的串一定是 \(00\dots 0011\dots 11\),每一次操作将前后都是 \(0\) 的连续 \(1\) 段和后缀的极长 \(1\) 段连起来,次数等价于 \(10\) 出现的次数。
综上所述,当 \(|T|=2\) 的时候,只需要数出 \(T\)\(S\) 中出现的次数,就是答案。
第三种,\(|T=3|\),根据题目给出的部分分,我们分为 \(T_1\ne T_3\)\(T_1=T_3,T_1\ne T_2\)\(T_1=T_2=T_3\)
\(T_1\ne T_3\) 的时候,我们不难发现和 \(T_1\ne T_2\) 时类似,一定都会将 \(0,1\) 分别拼在一起,形成前缀和后缀,同理可得,答案等价于 \(T\) 出现的次数。
\(T_1=T_3,T_1\ne T_2\) 的时候,我们不难发现每一次操作最多破坏两个 \(T\),不妨令第一个开始位置为 \(x\),第二个开始位置为 \(y\),那么我们选择 \([x+1,y]\) 的子串翻转即可,所以答案就是 \(\lceil cnt \rceil\),其中 \(cnt\) 表示 \(S\)\(T\) 出现的次数。
\(T_1=T_2=T_3\) 的时候,不妨令 \(T=000\),反之可以通过 \(0\)\(1\)\(1\)\(0\) 的操作化成这种情况。
\(T_1=T_2\) 时类似,我们发现合法的串一定满足相邻的两个 \(1\) 之间的 \(0\) 的个数不超过 \(2\) 个,因此如果令 \(2(cnt_1+1)<cnt_0\),那么无解,反之有解,其中 \(cnt_{0/1}\) 表示 \(S\) 中出现的次数。
考虑,如果存在 \(11\) 的结构,那么我们一次操作就可以将两个多余的 \(0\) 移进来,如果存在 \(101\) 的结构,那么我们一次操作就可以把一个多余的 \(0\) 移进来,但是因为一定有解,所以我们统计出 \(11\) 出现的次数 \(cnt\),对于一段长度大于 \(2\)\(0\) 的连续段,假设长度为 \(len\),那么需要的操作次数就是 \(len-2-\min(cnt,\lfloor \frac{len-2}{2}\rfloor)\),然后把 \(cnt\) 减去 \(\min(cnt,\lfloor \frac{len-2}{2}\rfloor)\) 即可。
最后得到时间复杂度 \(O(n)\) 的算法。

3. Code

/*by ChenMuJiu*/
/*略去缺省源与快读快写*/
const int N=2e5+5;
int n,m;
char S[N],T[5];
signed main(){
	scanf("%s",S+1);
	scanf("%s",T+1);
	n=strlen(S+1),m=strlen(T+1);
	if(m==1){
		for(int i=1;i<=n;i++){
			if(S[i]==T[1]){
				puts("-1");
				exit(0);
			}
		}
		puts("0");
	}
	if(m==2){
		if(T[1]!=T[2]){
			int ans=0;
			for(int i=1;i<n;i++)
				if(S[i]==T[1]&&S[i+1]==T[2])
					ans++;
			write(ans),Nxt;
		}else{
			if(T[1]=='1'){
				T[1]=T[2]='0';
				for(int i=1;i<=n;i++)
					S[i]^=1;
			}
			int cnt0=0,cnt1=0;
			for(int i=1;i<=n;i++){
				if(S[i]=='0')cnt0++;
				else cnt1++;
			}
			if(cnt0-cnt1>1){
				puts("-1");
				exit(0);
			}
			int ans=0;
			for(int i=1;i<n;i++)
				if(S[i]==T[1]&&S[i+1]==T[2])
					ans++;
			write(ans),Nxt;
		}
	}
	if(m==3){
		if(T[1]!=T[3]){
			int ans=0;
			for(int i=1;i<=n-2;i++)
				if(S[i]==T[1]&&S[i+1]==T[2]&&S[i+2]==T[3])
					ans++;
			write(ans),Nxt;
		}else if(T[1]!=T[2]){
			int ans=0;
			for(int i=1;i<=n-2;i++)
				if(S[i]==T[1]&&S[i+1]==T[2]&&S[i+2]==T[3])
					ans++;
			write((ans+1)/2),Nxt;
		}else{
			if(T[1]=='1'){
				T[1]=T[2]=T[3]='0';
				for(int i=1;i<=n;i++)
					S[i]^=1;
			}
			int cnt0=0,cnt1=0;
			for(int i=1;i<=n;i++){
				if(S[i]=='0')cnt0++;
				else cnt1++;
			}
			if((cnt1+1)*2<cnt0){
				puts("-1");
				exit(0);
			}
			int ans=0,cnt=0,blank2=0;
			S[0]=S[n+1]='1';
			for(int i=1;i<=n+1;i++){
				if(S[i]=='0')cnt++;
				else if(S[i]=='1'){
					if(cnt==0)blank2++;
					cnt=0;
				}
			}
			for(int i=0;i<=n+1;i++){
				if(S[i]=='0')cnt++;
				else if(S[i]=='1'){
					if(cnt>2){
						cnt-=2;
						int mi=min(cnt/2,blank2);
						blank2-=mi;
						cnt-=mi*2;
						ans+=mi+cnt;
					}
					cnt=0;
				}
			}
			write(ans),Nxt;
		}
	}
}
posted @ 2026-04-13 15:07  陈牧九  阅读(21)  评论(0)    收藏  举报