ZOJ - 4114 第十届山东省赛 - B - Flipping Game(DP)

在这里插入图片描述
在这里插入图片描述
sample input

3
3 2 1
001
100
3 1 2
001
100
3 3 2
001
100

sample output
2
1
7
hint
For the first sample test case, Little Sub can press the 1st switch in the 1st round and the 3rd switch in the 2nd round; Or he can press the 3rd switch in the 1st round and the 1st switch in the 2nd round. So the answer is 2.

For the second sample test case, Little Sub can only press the 1st and the 3rd switch in the 1st and only round. So the answer is 1.
题意:
给你一排n个灯泡,让你进行k轮操作,每轮操作可以点亮或熄灭m个不同的灯泡。然后给你这排灯泡的初态和末态,问你在k轮操作后能让这排灯泡到达末态的方案有多少种(对998244353取模)。

分析
给出两组状态,我们需要将第一组状态变成第二组的状态的话,可以发现这样一个问题,如果是不一样的地方需要变换奇数次,如果是相同一样的地方的话,那就是需要转化偶数次,那么根据这样的推理我们可以定义一个dp[i][j]数组表示第i轮需要变化奇数次的个数为j个;

选择变化的为奇数次的个数为j个,那么可以发现,假设这次有j个位置是奇数次,这次有p个位置从奇数次变成了偶数次,那么很明显,剩下m-p个位置就是从偶数次变成了奇数次;

当然j个里面需要挑选的p怎么挑选,就需要用到组合数了,
从j个里面选p个那么就是C(j,p),那剩下的m-p个就需要从n-j个里面选了,即C(n-j,m-p),那么就可以得到:

**dp[i+1][j-p+m-p]+=dp[i][j]c[j][p]c[n-j][m-p]

为啥下一个状态是dp[i+1][j-p+m-p]呢,因为从当前第i轮的时候是从j个里面选择了p个奇数翻转的,那么剩下了j-p个需要翻转奇数次的,而且还选中了m-p个需要翻转偶数次的,那么其在第i+1轮里面不就成为需要翻转奇数次的了吗,因此第i+1次就会变成有i-p+m-p个需要翻转奇数次的了

还有一个地方就需要注意的就是初始状态:
这个问题的第二个关键点在于初值的赋值。有两种显而易见的赋值方式,odd_num指的是两个串中状态不同的位置的个数,一种是开头dp[0][0],结束dp[k][odd_num],也就是刚开始没有奇数位变化的位,最后出现有奇数位变化,还有一种是dp[0][odd_num],dp[n][0],可以发现只有第二种是正确的,因为第一种到最后只能保证有odd_num个位发生变化,不能保证是哪几个,而如果第二种,就先保证了起始有m个位需要改变只有一种情况。

#include <iostream>
#include <algorithm>
#include <string>
#include <map>
#include <cstring>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <set>
#include <stack>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Abs(x) ((x)>=0?(x):-(x))
ll c[1000][1000];
char str1[200],str2[200];
ll dp[500][500];
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    c[0][0]=1;//这个地方注意一下,别漏了
    for(int i=1;i<=100;i++){//组合数打表
        c[i][0]=1;
        for(int j=1;j<i;j++)
        c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;
        c[i][i]=1;
    }

    int T,n,k,m;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d\n",&n,&k,&m);
        scanf("%s%s",str1,str2);
        int odd=0;
        for(int i=0;i<n;i++)
            if(str1[i]!=str2[i]) odd++;
        memset(dp,0,sizeof dp);
        dp[0][odd]=1;
        for(int i=0;i<k;i++){
            for(int j=0;j<=n;j++){
                for(int p=0;p<=m;p++){
                    if(p<=j&&n-j>=m-p){
        //这一行为啥是加上,而不是直接等于呢?因为j与2*p的关系有存在相等的情况,当
        //j=3,p=1与j=4,p=2的情况都是需要加到dp[i+1][m+2]的这种情况下,故是加号
                      dp[i+1][j-p+m-p]=dp[i+1][j-p+m-p]+(dp[i][j]*c[j][p]%MOD*c[n-j][m-p])%MOD;

                    dp[i+1][j-p+m-p]%=MOD;
                    }
                }
            }
        }
        printf("%lld\n",dp[k][0]);
    }
    return 0;
 }

本来在打省赛的时候看了这题面以为是组合数或者数学题,但是当时一直没想出来,现在补了一下,原来是DP,还是dp功夫不到家啊!!!!!
但是也感谢队友们的付出,感谢在赛前集训大家的付出与陪伴,这算是一个赛后的小总结吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值