首页 > 其他分享 >P9566 [SDCPC2023] K-Difficult Constructive Problem 题解

P9566 [SDCPC2023] K-Difficult Constructive Problem 题解

时间:2023-09-26 13:55:06浏览次数:47  
标签:return minn P9566 int 题解 SDCPC2023 修改 maxn 端点

@

目录

Description

有一个长度为 \(n\) 的 01字符串 \(s\),其中部分位置已给出,在 ?的位置处需填入一个 10

一个填充方案是好的,当且仅当存在 \(m\) 个不同的 \(i\) 满足 \(1\le i<n\) 且 \(s_i\ne s_{i+1}\)。

求所有好的填充方案中字典序最小的一个,如果无解输出 Impossible

对于两个长度为 \(n\) 的字符串 \(a,b\),若存在一个整数 \(k(1\le k\le n)\),使得所有 \(1\le i<k\) 有 \(a_i=b_i\) 且 \(a_k<b_k\),则称 \(a\) 的字典序小于 \(b\) 的字典序。

Solution

设当前 有 \(w\) 个 \(i\) 满足 \(1\le i<n\) 且 \(s_i\ne s_{i+1}\)。

引理:对于 \(1<i<n\),将 \(s_i\) 取反,\(w\) 的奇偶性不变,分类讨论证明:

  • \(101\rightarrow111,w=w-2\)
  • \(111\rightarrow101,w=w+2\)
  • \(010\rightarrow000,w=w-2\)
  • \(000\rightarrow010,w=w+2\)
  • \(100\rightarrow110,w=w\)
  • \(110\rightarrow100,w=w\)
  • \(011\rightarrow001,w=w\)
  • \(001\rightarrow011,w=w\)

Sol1

只有将 \(s_1\) 或 \(s_n\) 取反才能将 \(w\) 改变奇偶性。

所以将 \(s_1\) 和 \(s_n\) 固定后就不用考虑 \(w\) 的奇偶性。

对于整个字符串,一定存在 \(w\) 能取到的最大值和最小值,设分别为 \(w_1,w_2\)。

由引理得 \(2\mid w_1-w_2\),所以当 \(2\nmid m-w_2\) 时,合法填充一定无法做到 \(w=m\),输出 Impossible

如何求出 \(w_1\) 和 \(w_2\) 呢?

我们用 \(e\) 数组记录下一段连续的 ?的左端点 \(l\) 和右端点 \(r\),这里的端点指两边第一个不是 ?的字符。

对于一段 ?,我们分类讨论,其中 \(len\) 表示这一段 ?的个数:

  • \(l=r\),在全都填与左右端点相同时取到 \(w_2=0\),当 \(2\mid len\) 时,\(w_1=len\),否则 \(w_1=len+1\)

  • \(l\ne r\),在全都填与左右端点其中一个时取到 \(w_2=1\),当 \(2\mid len\) 时,\(w_1=len+1\),否则 \(w_1=len\)

可以在纸上举几个例子找规律。

我们用一个 \(b\) 数组记下字符串中为 ?的下标。

为了使答案字典序最小,我们先将所有 ?填上 0,算出此时的 \(w\)。

若 \(w=m\),直接输出当前字符串。

否则说明我们需要把某些 0(在 \(b\) 里面的)改为 1

因为是改为 1,所以字典序会增大,所以从后往前修改保证字典序更优。

设当前要修改第 \(i\) 位。

对于修改前 \(w<m\) 的情况,分三种情况讨论:

  • 若 \(s_{i-1}=s_i=s_{i+1}\) ,此时修改 \(s_i\),\(w\) 会加二,所以修改。

  • 若 \(s_{i-1}=s_{i+1}\ne s_i\),此时修改 \(s_i\),\(w\) 会减二,不仅离答案越远,字典序也变大,所以不修改。

  • 若 \(s_{i-1}\ne s_{i+1}\),此时修改 \(s_i\),\(w\) 不变,但字典序变大,所以不修改。

对于修改前 \(w>m\) 的情况,只有一种情况。因为能修改的改完后只能是 1,所以只有当 \(s_{i-1}=s_{i+1}=1\) 时,修改后 \(w\) 才会减二。

在修改前,我们已经记录了每一个连续 ?的段的左端点和右端点,若一段的左右端点都是 1,则将这一整段 ?都改为 1

Code1

#include<bits/stdc++.h>
using namespace std;
int t,n,m;
int num[1000010];
int a[1000010];
int b[1000010];
int tot,cnt;
bool f=0;
int maxn,minn;
struct node{
	int l,r;
}e[1000010];
void clean(){  //多测清空
	tot=cnt=f=0;
}
void print(){
	for(int i=1;i<=n;i++){
		cout<<a[i];
	}
	cout<<endl;
}
void solve(int x,int y,int z,int w){
	maxn=0,minn=0;
	for(int i=1;i<=n;i++){
		a[i]=num[i];
	}
	if(x){
		a[x]=y;
	} 
	if(z){
		a[z]=w;
	}
	for(int i=1;i<n;i++){
		if(a[i]!=a[i+1]&&a[i]!=2&&a[i+1]!=2){  //不是?的连续段,w1和w2是确定的
			minn++;
			maxn++;
		}
	}
	for(int i=1;i<=cnt;i++){
		int len=e[i].r-e[i].l-1;  
		maxn+=len;
		if(a[e[i].l]==1&&a[e[i].r]==1) maxn+=(len%2==1)?1:0;
		else if(a[e[i].l]==0&&a[e[i].r]==0) maxn+=(len%2==1)?1:0;
		else maxn+=(len%2==1)?0:1,minn+=1;
	}
	if(m<minn||m>maxn||(m-minn)%2!=0){
		return;
	}
	int now=0;
	for(int i=1;i<=tot;i++){
		a[b[i]]=0;
	}
	for(int i=1;i<n;i++){
		if(a[i]!=a[i+1]) now++;
	}
	if(now==m){
		print();
		f=1;
		return ;
	}
	if(now<m){
		for(int i=tot;i>=1;i--){
			if(now==m){
				print();
				f=1;
				return ;
			}
			if(a[b[i]+1]==0&&a[b[i]-1]==0){
				a[b[i]]=1;
				now+=2;
			}
		}
		if(now==m){
			print();
			f=1;
		}
		return ;
	}
	if(now>m){
		for(int i=cnt;i>=1;i--){
			if(now==m){
				print();
				f=1;
				return ;
			}
			if(a[e[i].l]==1&&a[e[i].r]==1){
				for(int j=e[i].l+1;j<e[i].r;j++){
					a[j]=1;
				}
				now-=2;
			}
		}
		if(now==m){
			print();
			f=1;
		}
		return ;
	}
}
void sol(){
	clean();
	cin>>n>>m;
	string s;
	cin>>s;
	for(int i=1;i<=n;i++){
		if(s[i-1]=='?') num[i]=2;
		else num[i]=s[i-1]-'0';
	}
	bool ff=0;
	int last=1;
	for(int i=2;i<n;i++){	
		if(num[i]==2){  //将?的下标存起来
			b[++tot]=i;
			ff=1;
		}else{
			if(ff){
				e[++cnt].l=last;  //记录?连续段的左端点和右端点
				e[cnt].r=i;  //注意,这里的端点实际上并不是?,是?外的第一个点
			}
			last=i;
			ff=0;
		}
	}
	if(ff){
		e[++cnt].l=last;
		e[cnt].r=n;
	}
	if(num[1]==2&&num[n]==2){  //枚举字符串两端的情况
		solve(1,0,n,0);
		if(f) return ;  //一旦满足条件,直接输出,此时字典序最小
		solve(1,0,n,1);
		if(f) return ;
		solve(1,1,n,0);
		if(f) return ;
		solve(1,1,n,1);
		if(f) return ;
		printf("Impossible\n");
		return ;
	}
	if(num[1]==2){
		solve(1,0,0,0);
		if(f) return ;
		solve(1,1,0,0);
		if(f) return ;
		printf("Impossible\n");
		return ;
	}
	if(num[n]==2){
		solve(0,0,n,0);
		if(f) return ;
		solve(0,0,n,1);
		if(f) return ;
		printf("Impossible\n");
		return ;
	}
	solve(0,0,0,0);
	if(f) return ;
	printf("Impossible\n");
}
int main(){
	cin>>t;
	while(t--){
		sol();
	}
	return 0;
}

这一种解法代码量大,细节多,所以接下来介绍一种更简洁的做法。

Sol2

设 \(maxn_{i,j}\) 表示第 \(j\) 位为 \(i\) 时,\(s_{j\sim n}\) 的 \(w_1\) 值, \(minn_{i,j}\) 表示第 \(j\) 位为 \(i\) 时,\(s_{j\sim n}\) 的 \(w_2\) 值。

所以若 \(s_j\) 能填 \(i\),当且仅当 \(minn_{i,j}\le res\le maxn_{i,j}\) 且 \(2\mid res-mi\)(\(s_n\) 为 ?时例外,因为可以改奇偶性)。注意:这里没有考虑 \(s_j\) 本身是否为 ?,具体原因往下看。

根据定义,得出 \(maxn,minn\) 的递推式:

  • \(maxn_{i,j}=\max(maxn_{i,j+1},maxn_{1-i,j+1}+1)\)

  • \(minn_{i,j}=\min(minn_{i,j+1},minn_{1-i,j+1}+1)\)

递推时从 \(n\) 推向 \(i\)。

当 \(s_j\) 不为 ?时,将 \(maxn_{1-s_j,j}=1\times 10^8,minn_{1-s_j,j}=-1\times 10^8\),这样就能保证上面判断 \(s_j\) 能否填 \(i\) 时,填 \(1-s_j\) 肯定不合法。

如果 \(s_1\) 既不能填 0也不能填 1,输出 Impossible,因为第一位什么都不能填,肯定不满足条件,但如果第一位可以填,就说明存在合法解,后面就不用再判无解。

然后一位位的判能否填 0,不能就填 1

Code2

#include<bits/stdc++.h>
using namespace std;
int t;
int n,m;
int a[1000010];
int maxn[2][1000010],minn[2][1000010]; //第j位为i时的最大最小值 
bool check(int x,int y){
	int cnt=m;
	if(x>1&&y!=a[x-1]) cnt--; //和前面一位不同时,w加了1,所以这里要减1
	if(cnt<minn[y][x]||cnt>maxn[y][x]) return 0;
	if(a[n]!=2&&(cnt-minn[y][x])%2==1) return 0;
	return 1; 
}
void clean(){
	for(int i=1;i<=n;i++){
		maxn[0][i]=maxn[1][i]=0;  //多测清空
		minn[0][i]=minn[1][i]=0;
	}
}
void solve(){
	clean();
	cin>>n>>m;
	string s;
	cin>>s;
	for(int i=0;i<n;i++){
		if(s[i]=='?') a[i+1]=2;
		else a[i+1]=s[i]-'0';
	}
	for(int i=n;i>=1;i--){
		maxn[0][i]=max(maxn[0][i+1],maxn[1][i+1]+1); 
		minn[0][i]=min(minn[0][i+1],minn[1][i+1]+1);
		maxn[1][i]=max(maxn[1][i+1],maxn[0][i+1]+1);
		minn[1][i]=min(minn[1][i+1],minn[0][i+1]+1);
		if(i==n) maxn[1][i]=minn[1][i]=maxn[0][i]=minn[0][i]=0; //特判i==n
		if(a[i]==0){
			minn[1][i]=100000000;
			maxn[1][i]=-100000000;
		}else if(a[i]==1){
			minn[0][i]=100000000;
			maxn[0][i]=-100000000;
		}
	}
	if(check(1,0)==0&&check(1,1)==0){
		cout<<"Impossible"<<endl;
		return ;
	}
	for(int i=1;i<=n;i++){
		if(check(i,0)) a[i]=0;
		else a[i]=1;
		if(i>1&&a[i]!=a[i-1]) m-=1;
	}
	for(int i=1;i<=n;i++){
		cout<<a[i];
	}
	cout<<endl;
}
int main(){
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

标签:return,minn,P9566,int,题解,SDCPC2023,修改,maxn,端点
From: https://www.cnblogs.com/larryyu/p/17729931.html

相关文章

  • 预训练Bert模型输出类型为str问题解决
     input_ids=keras.layers.Input(shape=(MAXLEN,),dtype='int32')attention_mask=keras.layers.Input(shape=(MAXLEN,),dtype='int32')token_type_ids=keras.layers.Input(shape=(MAXLEN,),dtype='int32')_,x=bert_model([input_ids,atten......
  • 浏览器输入 http 自动转 https 问题解决方法
    很多朋友问浏览器输入http被自动跳转至https问题,到底该怎么解决呢,其实解决方法很简单,主要关闭浏览器的HSTS功能就可以了IE浏览器1.地址栏中输入edge://net-internals/#hsts2.在Deletedomain中输入项目的域名,并Delete(删除)3.可以在Querydomain测试是否删除成功。Chrome浏览......
  • Redis大key问题解决方案
     Redis的大key如何处理 介绍 大key并不是指key的值很大,而是key对应的value很大(非常占内存)一般而言,下面这两种情况被称为大key:String类型的值大于10KB;Hash、List、Set、ZSet类型的元素的个数超过5000个;为什么会出现大key数据结构不合理:当使用Re......
  • 综合概念映射和网络问题解决方法对学生学习成绩、感知和认知负荷的影响
    (Effectsofanintegratedconceptmappingandweb-basedproblem-solvingapproachonstudents’learningachievements,perceptionsandcognitiveloads) Computers&Education71(2014)77–86一、摘要研究目的:虽然学生可以通过适当的关键词有效地搜索到网络数据,并......
  • CF1863 题解
    CF1863题解A条件很简单:如果总共的'+'号加上开始上线人数不到\(n\)人,就不可能。实时记录人数,如果某一时刻大于等于\(n\)人在线上,就一定是。剩余情况则可能。#include<bits/stdc++.h>usingnamespacestd;intmain(){ intn,a,q,T; cin>>T; while(T--) { cin>>n>......
  • CF249E Endless Matrix 题解
    @目录Description前置芝士SolutionCodeDescription构造一类矩形:先构造矩形\(M_1=\begin{bmatrix}1\end{bmatrix}\)。对于\(i\geq1\),\(T_{i+1}\)从\(T_i\)构造而来,方法为在最右侧和最下侧插入新的一行一列,自右上到左下\(2i+1\)个数分别填入\(i^2+1,i^2+2\dots(i+1)^2\)......
  • nacos注册服务时网卡选择错误的问题解决方案
    nacos注册服务时网卡选择错误的问题解决方案如果本地或者服务器有安装虚拟机或者虚拟网卡,会导致应用注册nacos注册中心,导致ip错误的问题,解决方案就是在应用中增加对应配置spring:cloud:inetutils:preferredNetworks:-192.168......
  • CF1106D Lunar New Year and a Wander 题解
    CF1106D题解暑期学校军训第一天模拟赛的题,相对而言比较简单题意:题意其实很简单,就是有一个无向图,需要你从\(1\)号节点出发,然后一次遍历所有的点,输出其中字典序最小的遍历思路说说思路吧,这题既然要遍历图上所有点,那首先就会想到\(\texttt{BFS}\)或\(\texttt{DFS}\),可本题还......
  • ciscn_2019_c_1 题解
    main函数如下:int__cdeclmain(intargc,constchar**argv,constchar**envp){intv4;//[rsp+Ch][rbp-4h]BYREFinit(argc,argv,envp);puts("EEEEEEEhhiii");puts("EEmmmmmmm......
  • [JOISC 2014] 電圧 题解
    [JOISC2014]電圧题解赛时都想到了我也不知道为啥自己没敢写首先题意可以转化为,我们去掉一个边后,剩下的图可以黑白染色,同时保证去掉的边两端的点颜色相同,问这样的边数。换句话说,去掉一条边后,剩下的图应该是一个二分图。然后我们很容易想到线段树分治来处理这种问题。每次只有......