专栏文章

题解:P11361 [NOIP2024] 编辑字符串

P11361题解参与者 4已保存评论 3

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
3 条
当前快照
1 份
快照标识符
@miqxi6n3
此快照首次捕获于
2025/12/04 12:21
3 个月前
此快照最后确认于
2025/12/04 12:21
3 个月前
查看原文

题意理解:

  • 交换相邻字符:连续的 c[i]=1c[i]=1a[i]a[i] 位置可以任意调换顺序,连续的 d[i]=1d[i]=1b[i]b[i] 位置可以任意调换顺序。
  • 目标:使对应位置字符尽可能相等。

思路:

已知 a[i]a[i]b[i]b[i] 只有以下三种对应情况:
  • c[i]=d[i]=0c[i]=d[i]=0 时,此时 a[i]a[i]b[i]b[i] 字符已经锁死,因此只需要比较对应位置字符是否相同即可,如果相同就使 ans++ans++
  • c[i]=d[i]=1c[i]=d[i]=1 时,此时 a[i]a[i]b[i]b[i] 都不确定,下面会讨论。
  • c[i]=0c[i]=0d[i]=1d[i]=1c[i]=1c[i]=1d[i]=0d[i]=0 时,有一个字符确定,要尽量使另一个字符去迎合锁定字符,下面再进行讨论。
现在问题变成了可移动字符应该如何移动?
由于连续的可移动字符可以任意调换顺序,因此可以将连续的可移动字符看作一块。
因此原序列就变成了若干个可调换顺序的字符块与锁定字符的序列。在划分块的时需要记录在当前位置 ii 的字符属于哪个可移动块,并且记录当前块有多少个 1100
接下来再考虑如何调换。
首先考虑 c[i]=0c[i]=0d[i]=1d[i]=1c[i]=1c[i]=1d[i]=0d[i]=0 的情况。我们只需要考虑可移动的块内是否拥有当前不可移动的字符。如果有,则使当前可移动块的对应字符个数减 11,并使 ans++ans++,相当于将这个字符填在了这个位置。
接着考虑 c[i]=d[i]=1c[i]=d[i]=1 的情况,此时这个位置都在可移动块内,只需分别比对对应块的 11 的个数以及 00 的个数,如果都有 11 或者都有 00,就在这个位置填上 11 或者 00,并使 ans++ans++,两个可移动块对应字符个数分别减 11
至此,问题已经解决,请看代码实现:
CPP
#include<bits/stdc++.h>
using namespace std;
int t,n;
char a[1145140],b[1145140],c[1145140],d[1145140];
int loca[1145140],locb[1145140];//a[i]与b[i]当前位置为第几块
int blocka[1145140][3],blockb[1145140][3];//记录第某一块的字符0的总数与1的总数 
int main(){
	freopen("edit.in","r",stdin);
	freopen("edit.out","w",stdout);
	cin>>t;
	while(t--){
		int ans=0;//记录答案 
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		for(int i=1;i<=n;i++)cin>>b[i];
		for(int i=1;i<=n;i++)cin>>c[i];
		for(int i=1;i<=n;i++)cin>>d[i];
		//输入
		int cnta=1;//cnta记录当前为第几块 
		for(int i=1;i<=n;i++){
			if(c[i]=='1'){
				loca[i]=cnta;//记录当前位置属于哪个块 
				blocka[cnta][a[i]-'0']++;//块内对应字符个数+1 
			}
			if(c[i]=='0'&&c[i-1]=='1'){
				cnta++;//此块已经结束,开一个新块 
			}
		}
		int cntb=1;//b[i]与a[i]同理进行处理 
		for(int i=1;i<=n;i++){
			if(d[i]=='1'){
				locb[i]=cntb;
				blockb[cntb][b[i]-'0']++;
			}
			if(d[i]=='0'&&d[i-1]=='1'){
				cntb++;
			}
		}
		
		for(int i=1;i<=n;i++)if(c[i]=='0'&&d[i]=='0'&&a[i]==b[i])ans++;//当两位置锁定且相同时ans++
		
		for(int i=1;i<=n;i++){
			if(c[i]=='1'&&d[i]=='0'){
				if(blocka[loca[i]][b[i]-'0']!=0){//若当前块存在与锁定字符相同的字符时,将此字符填入 
					blocka[loca[i]][b[i]-'0']--;
					ans++;
				}
			}
			if(c[i]=='0'&&d[i]=='1'){
				if(blockb[locb[i]][a[i]-'0']!=0){//同理
					blockb[locb[i]][a[i]-'0']--; 
					ans++;
				}
			}
		}
		
		for(int i=1;i<=n;i++){
			if(c[i]=='1'&&d[i]=='1'){
				if(blocka[loca[i]][1]!=0&&blockb[locb[i]][1]!=0){
					blocka[loca[i]][1]--;
					blockb[locb[i]][1]--;
					ans++;
				}
				else if(blocka[loca[i]][0]!=0&&blockb[locb[i]][0]!=0){
					blocka[loca[i]][0]--;
					blockb[locb[i]][0]--;
					ans++;
				}
			}
		}
		cout<<ans<<endl;
		
		for(int i=1;i<=n;i++)loca[i]=0,locb[i]=0;
		//多测不清空,亲人两行泪 
		for(int i=1;i<=cnta;i++)blocka[i][0]=0,blocka[i][1]=0;
		//多测不清空,亲人两行泪 
		for(int i=1;i<=cntb;i++)blockb[i][0]=0,blockb[i][1]=0;
		//多测不清空,亲人两行泪
		//重要的事情说三遍 
	}
	return 0;
} 
这可能是真正意义上的第一次场切蓝题,但这也可能是最后一场比赛了,珍惜和 OI 在一起的时光吧,继续走下去。

评论

3 条评论,欢迎与作者交流。

正在加载评论...