Codeforces 1382 / Codeforces Round #658 【部分题解】

Div. 2 A

Statement

\(a,b\) 两数列的最短公共子序列。

如果存在,输出 YES,第二行输出其长度以及该子序列;如果不存在,输出 NO

\(|a|,|b| \leq 1000\)

Solution

如果存在,显然长度为 \(1\)。枚举即可。

Code

# include <bits/stdc++.h>
# define rr
const int N=1010,INF=0x3f3f3f3f;
int tota[N],totb[N];
int n,m;
int a[N],b[N];
int T;
inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
int main(void){
	T=read();
	while(T--){
		memset(tota,0,sizeof(tota)),memset(totb,0,sizeof(totb));
		n=read(),m=read();
		for(rr int i=1;i<=n;++i){
			a[i]=read(),++tota[a[i]];
		}
		for(rr int i=1;i<=m;++i){
			b[i]=read(),++totb[b[i]];
		}
		int ans=-1;
		for(rr int i=1;i<=1000;++i){
			if(tota[i]&&totb[i]){
				ans=i;
				break;
			}
		}
		if(~ans){
			printf("YES\n1 %d\n",ans);
		}else{
			printf("NO\n");
		}
	}

	return 0;
}

Div.2 B

Statement

给定 \(n\) 堆石子,第 \(i\) 堆有 \(a_i\) 个。先后手轮流取,每次取编号最小非空堆,至少取一个。问最优操作下谁必胜。

\(n \leq 100000,a_i \leq 10^9\)

Solution

\(a_1>1\) 的时候,如果拿完第一堆必胜,那么先手会这么做;如果拿完第一堆必败,那么先手会留下一个,迫使后手拿完第一堆。综上,当 \(a_1 >1\) 时,先手必胜。

但如果前缀中有奇数个 \(1\),那么胜负就会反转:后手会操作第一个 \(>1\) 的堆,按照上面的策略让自己胜利。

这样有一个特殊情况:所有的堆都只有一个石子。这样判断 \(n\) 的奇偶性即可。

Code

# include <bits/stdc++.h>
# define rr
const int N=100010,INF=0x3f3f3f3f;
int n;
int a[N];

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
int main(void){
	int T=read();
	while(T--){
		n=read();
		bool flag=true;
		int tot=0;
		for(rr int i=1;i<=n;++i){
			a[i]=read();
			flag&=(a[i]==1);
			if(flag){
				++tot;
			}
		}
		if(flag){
			puts((n%2)?"First":"Second");
		}else{
			puts((tot%2)?"Second":"First");
		}

	}

	return 0;
}

Div. 1 A (Hard Version)

Statement

给定长为 \(n\)\(01\)\(a,b\)。每次可以选择 \(a\) 的一个前缀,将其取反(\(1\)\(0\)\(0\)\(1\)),然后该前缀翻转。

例如,\(a=001011\),选择前缀为 \(3\) 的前缀操作,\(a\) 变为 \(011011\)。然后选择整个字符串操作,\(a\) 变成 \(001001\)

使用不超过 \(2n\) 次操作将 \(a\) 变成 \(b\)

Solution

\(n\) 次操作,考虑将 \(a\) 变成全 \(0/1\)。具体来讲,如果 \(a\) 具有相同颜色的最长前缀颜色长度为 \(i(i<n)\),那么可以使用操作将该前缀的颜色取反,得到一个长度至少为 \(i+1\) 的具有相同颜色的最长前缀。

例如,\(a=01101\)

  • 将长度为 \(1\) 的前缀颜色取反,\(a=11101\)
  • 将长度为 \(3\) 的前缀颜色取反,\(a=00001\)
  • 将长度为 \(4\) 的前缀颜色取反,\(a=11111\)

因为每取反一次,具有相同颜色的最长前缀的长度就增加 \(1\),所以至多 \(n\) 次就可以将 \(a\) 变成全 \(0/1\)

\(n\) 次操作,将 \(a\) 变成 \(b\)。例如,\(a=111111,b=011010\)。我们从后往前操作 \(b\) 中每一个具有相同颜色的连续段。

  • \(b_6=0\),于是我们将 \(a\) 全部取反,\(a=000000\)
  • \(b_5=1\),将长度为 \(5\) 的前缀颜色取反,\(a=111110\)
  • \(b_4=0\),将长度为 \(4\) 的前缀颜色取反,\(a=000010\)
  • \(b_{2,3}=1\),将长度为 \(3\) 的前缀颜色取反,\(a=111010\).
  • \(b_1=0\),将长度为 \(1\) 的前缀颜色取反,\(a=011010\)

可以想象在一个颜色板上不断地涂上新颜色的操作。

由于 \(b\) 中至多有 \(n\) 个不同的颜色段,所以至多 \(n\) 次就可以将 \(a\) 变成 \(b\)

# include <bits/stdc++.h>
# define rr
const int N=200010,INF=0x3f3f3f3f;
char a[N],b[N];
int ans[N],cnt;
int n;
inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
int main(void){
	int T=read();
	while(T--){
		n=read();
		scanf("%s",a+1);
		scanf("%s",b+1);
		char now=a[1];
		int cnt=0;
		for(rr int i=2;i<=n;++i){
			if(a[i]!=now){
				ans[++cnt]=i-1,now=a[i];
			}
		}
		char suv=b[n];
		int st=n;
		for(rr int i=n-1;i;--i){
			if(suv!=b[i]){
				if(now!=suv){
					ans[++cnt]=st;
					now=suv;
				}
				suv=b[i];
				st=i;
			}
		}
		if(b[1]!=now){
			ans[++cnt]=st;
		}
		printf("%d ",cnt);
		for(rr int i=1;i<=cnt;++i){
			printf("%d ",ans[i]);
		}
		puts("");
	}
	return 0;
}

Div.1 B

Statement

对于两个数组 \(a,b\),定义 \(\text{merge}(a,b)\)

\[ \text{merge}(a,b)=\left\{ \begin{aligned} \empty & & a=b=\empty \\ a & & b=\empty \\ b & & a=\empty \\ a_1+\text{merge}([a_2,...a_n],b) & & a_1<b_1 \\ b_1+\text{merge}(a,[b_2,...b_m]) & & a_1>b_1 \end{aligned} \right. \]

给定长为 \(2n\) 的排列 \(p\),问是否存在长为 \(n\)\(a,b\) 使得 \(\text{merge}(a,b)=p\)

\(n \leq 2000\)

Solution

\(p\) 分段。设 \(p_{2n+1}=\infty\),将 \(p_1,p_2,...,p_k(p_1=\max\{p_1,p_2,...,p_k\},p_{k+1}>p_1)\) 分在一起,然后从 \(p\) 中去除它们。

容易发现,每一段的数必须分在一个数组中。如果不这么做,一定会导致 \(\text{merge}(a,b)\) 的结果中,每一段中第一个数出现的位置比后面的数晚,因为第一个数是这一段的最大值。

求出每一段的长度,然后可以使用简单的动态规划来判断是否有若干段的长度和正好为 \(n\)。如果存在,那么就有至少一组合法解。

Code

# include <bits/stdc++.h>
# define rr
const int N=5010,INF=0x3f3f3f3f;
int T;
int val[N],dp[N];
int n;
int g[N],m;
inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
int main(void){
	T=read();
	while(T--){
		n=read();
		for(rr int i=1;i<=2*n;++i){
			val[i]=read();
		}
		m=0;
		g[++m]=1;
		int now=val[1];
		for(rr int i=2;i<=2*n;++i){
			if(now>val[i]){
				++g[m];
			}else{
				now=val[i],g[++m]=1;
			}
		}
		memset(dp,0,sizeof(dp)),dp[0]=1;
		for(rr int i=1;i<=m;++i){
			for(rr int j=n;j>=g[i];--j){
				dp[j]|=dp[j-g[i]];
			}
		}
		puts(dp[n]?"YES":"NO");
	}

	return 0;
}
原文地址:https://www.cnblogs.com/liuzongxin/p/13360744.html