[AGC046C] Shift 题解

[AGC046C] Shift

题目大意

给定一个01串\(S\)。每次操作可将一个1移到它左边的任意一个0的左边。求进行不多于给定整数\(k\)此操作所得的不同串个数(对\(998244353\)取模)。

数据范围:\(1\leq |S|\leq 300, 0\leq k\leq 10^9\)

题解

\(n=|S|\)。易知至多进行\(n\)次操作就可以达到所有能达到的状态,于是\(k\geq n\)时的答案和\(k=n\)是的答案是一样的,那么就可以把\(k\)的范围降到\(300\)了。

接着考虑用另一种方式表示一个01串:记\(cnt_i\)为有多少个1的左边恰好有\(i\)0。容易证明,相同的cnt数组对应相同的01串,不同的cnt数组对应不同的01串。

然后考虑每一次操作对\(cnt\)数组的影响:选择两个位置\(i,j(i< j)\),使\(cnt_i\leftarrow cnt_i+1, cnt_j\leftarrow cnt_j-1\)(记此操作为\(j\rightarrow i\))。

于是就可以愉快地dp了:设\(f_{i,j,x}\)为总变化数为\(x\)(一次操作产生两次变化),共进行\(j\)\([i+1,n]\rightarrow [0,i]\)的操作所产生的的不同方案数。那么转移方程为:

\[f_{i,j,x}=\sum_{0\leq y\leq x}f_{i-1,j-y,x-y}+\sum_{1\leq y\leq \min\{n-j,x,cnt_i\}}f_{i-1,j+y,x-y} \]

其中前一部分为进行\(y\)\([i+1,n]\rightarrow i\)操作所产生的贡献,后一部分为进行\(y\)\(i\rightarrow [0,i-1]\)操作所产生的贡献。

注意用两个数组储存一下两个求和号,然后直接递推即可。时间复杂度\(O(n^3)\)

代码(为实现方便,一些数组进行了平移处理):

#include<cstdio>
#include<cstring>
#include<algorithm>
#define P 998244353
#define N 305

char s[N];
int n,k;

int cnt[N];

int dp[N][N][N<<1],s1[N][N<<1],s2[N][N<<1],ans;

int main(){
	scanf("%s%d",s+1,&k);
	n=strlen(s+1);
	k=std::min(k,n)<<1;
	for(int i=1,tmp=0;i<=n;i++)
		if(s[i]=='0')
			tmp++;
		else
			cnt[tmp+1]++;
	dp[0][0][0]=1;
	for(int j=0;j<=n&&j<=k;j++)
		s1[j+1][j+1]=1;
	s2[1][1]=1; 
	for(int i=1;i<=n+1;i++){
		for(int j=0;j<=n;j++)
			for(int x=0;x<=k;x++){
				dp[i][j][x]=s1[j+1][x+1];
				int y=std::min(std::min(n-j,x),cnt[i]);
				if(y)
					dp[i][j][x]=((dp[i][j][x]+s2[j+2][x])%P+P-s2[j+2+y][x-y])%P;
			}
		for(int j=0;j<=n;j++)
			for(int x=0;x<=k;x++)
				s1[j+1][x+1]=(s1[j][x]+dp[i][j][x])%P;
		for(int j=n;j>=0;j--)
			for(int x=0;x<=k;x++)
				s2[j+1][x+1]=(s2[j+2][x]+dp[i][j][x])%P;
	}
	for(int x=0;x<=k;x++)
		ans=(ans+dp[n+1][0][x])%P;
	printf("%d",ans);
}
posted @ 2020-08-21 22:57  Y25t  阅读(281)  评论(0编辑  收藏  举报