Time Limit : 1 Second Memory Limit : 65536 KB
Source : 第十届山东省ACM省赛
Problem Link : ZOJ 4114
Author : Houge Date : 2019-5-21
题目大意:
给你一排n个灯泡,让你进行k轮操作,每轮操作可以点亮或熄灭m个不同的灯泡。然后给你这排灯泡的初态和末态,问你在k轮操作后能让这排灯泡到达末态的方案有多少种(对998244353取模)。
分析:
DP,学习了点我跳转XD的代码和思路。这个题我们在比赛的时候也想过用dp,不过主攻dp的我太菜了写不出状态转移方程,汗颜啊。。。
先来考虑状态转移方程:
·我们可以这样想:对于同一个灯泡来说,如果它初态和末态的状态一样,那么可以认为在这个过程中它被操作了偶数次,反之则认为它被操作了奇数次,可以用odd_num来记录奇数次操作的灯泡的个数。
·然后我们声明一个二维数组dp[i][j],代表经过i轮操作过后有j个灯泡被操作了奇数次的方案数。
·接着我们来想一下它的下一轮操作:从j个奇数次操作的灯泡中选出k个,使它们变成偶数次操作;然后偶数次操作的灯泡中会有m-k个灯泡变成奇数次操作,即下一个状态为:dp[i+1][j-k+m-k]。
·最后利用组合数便可得到状态转移方程:
(注意要取模)
然后再来考虑一下初值的问题:
·对于dp中所有元素的初值,可先赋值为0。
·对于初态和末态,第一种方法是将初态设为dp[0][0]=1,最后输出末态dp[k][odd_num],但这样会有一个问题,初态是明确的,但是末态时虽然奇数次操作灯泡的个数时对的,但是并不能确定是哪几个灯泡,会有错误(*经过测试应该会输出oddnum个数的所有方案和)。
·所以有了第二种方法,将初态设为dp[0][odd_num]=1,最后输出的是dp[k][0],这样初末态就都明确了。
最后考虑几个细节问题:
·推荐用long long,因为最后答案比较大随时可能爆掉(未测试)。
·在计算时随时取模防止爆。
·在求组合数的时候也要取模(我在后来自己写的时候就是这个地方卡住了,最后还是看的题解才发现的)。
到这儿这个问题就基本解决了~
代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 const int MAXN=105,MOD=998244353; 6 long long c[MAXN][MAXN],dp[MAXN][MAXN]; 7 8 int main() 9 { 10 long long i,j,k; 11 for(i=0;i<MAXN;i++) //求组合数 12 for(j=0;j<=i;j++) c[i][j]=1; 13 for(i=2;i<MAXN;i++) 14 for(j=1;j<i;j++) 15 c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD; //要取模! 16 17 int t; 18 scanf("%d",&t); 19 while(t--) 20 { 21 memset(dp,0,sizeof(dp)); //初始化 22 long long n,r,m,oddnum=0; 23 char curr[MAXN],dist[MAXN]; 24 scanf("%lld%lld%lld%s%s",&n,&r,&m,curr,dist); 25 26 for(i=0;i<n;i++) 27 if(curr[i]!=dist[i]) oddnum++; 28 29 dp[0][oddnum]=1; 30 31 for(i=0;i<r;i++) //dp 32 for(j=0;j<=n;j++) 33 for(k=0;k<=m;k++) 34 { 35 if(j>=k&&n-j>=m-k) 36 { 37 dp[i+1][j-k+m-k]=dp[i+1][j-k+m-k]+(dp[i][j]*c[j][k]%MOD*c[n-j][m-k]%MOD); 38 dp[i+1][j-k+m-k]%=MOD; 39 } 40 } 41 printf("%lld ",dp[r][0]); 42 } 43 return 0; 44 }