• [NOI2013模拟] BZOJ4705 棋盘游戏 解题报告(组合计数)


    莫名打不开这道题的链接,请读者自行搜索

    Description

    有一个N*M的棋盘,初始每个格子都是白色的。
    行操作是指选定某一行,将这行所有格子的颜色取反(黑白互换)。
    列操作是指选定某一列,将这列所有格子的颜色取反。
    XX进行了R次行操作C次列操作(可能对某行或者某列操作了多次),最后棋盘上有S个黑色格子。
    问有多少种不同的操作方案。两种操作方案不同,当且仅当对某行或者某列操作次数不同(也就是说与操作的顺序无关)。
    方案数可能很大,输出它对10^9+7取模的结果。
     

    Input

    输入只有5个整数N,M,R,C,S。

    Output

    输出有且仅有一个整数,表示答案对10^9+7取模的结果。
     

    Sample Input

    2 2 2 2 4

    Sample Output

    4
     

    Data Constraint

    对于20%的数据,满足N,M,R,C≤4。
    对于60%的数据,满足N,M,R,C≤1500。
    对于100%的数据,满足N,M,R,C≤100000,0≤S≤N*M。
     
     
    其实题目里面已经给了提示了,对于行和列操作,顺序什么的是无所谓的,也就是说我们只需要考虑最终有几行被翻转了奇数次,几列被翻转了奇数次就可以统计答案了。
    我们设有i行被翻转了奇数次,j列被翻转了奇数次,且最终有s个黑格,可以得到:
    i*m+c*n-2*i*j=s(很好理解,有点类似于容斥,可自行模拟几组看看)
    于是我们可以依次枚举i的值,就能对应的算出j的值。(当然i,j是有范围的,细节见程序)
    现在问题变成我们知道i,j怎么统计方案数
    重新说明i的含义:对于n行,我们进行r次操作,有i行被操作了奇数次。
     
     
    分成两种情况(p=i,q=j)
    1. 2*p=n,我们发现此时j的计算式的分母为0,这意味着此时j在范围内随意取值。注意此时s必须等于n*m/2(也请读者模拟几组数组,这是显而易见的)
    calc1=C(n,p)*C((r-p)/2+n-1,n-1)*C(c+m-1,m-1)

    2. 2*p<>n

    方案数calc2=C(n,p)*C(m,q)*C((r-p)/2+n-1,n-1)*C((c-q)/2+m-1,m-1)

    想必不能理解的形如C((r-p)/2+n-1,n-1)这样的式子吧。下面解释它的含义:我们有r次操作,最后有p点贡献的,可以假设我们有r个球,每次我们可以把两个球同时消去(称为合并操作),最后我们还剩下p个球,这是因为对于一行进行两次操作就相当于没有操作。那么(r-p)/2就是我们合并操作的个数。在之后我们就可以任意把这(r-p)/2个操作分配给n行了,方案数就是C((r-p)/2+n-1,n-1)(每一次合并操作其实就是两次翻转)

    对于这个分配的方案数不理解的,可以想象把n个一样的球分到m个不同的箱子里的方案数,可参考下面博客https://blog.csdn.net/qwb492859377/article/details/50654627

    C的话预处理出阶乘的逆元就好

    #include<cstdio>
    #include<algorithm>
    #define ll long long
    using namespace std;
    
    const int mod=1e9+7;
    const int maxn=2e5+15;
    int n,m,r,c,MAX;
    ll s;
    int jie[maxn],inv[maxn];
    int power(int x,int y)
    {
        int ans=1;
        for (;y;y>>=1,x=1ll*x*x%mod)
            if (y&1) ans=1ll*ans*x%mod;
        return ans;
    }
    int C(int m,int n)
    {
        if (m<0||n<0||n>m) return 0;
        return 1ll*jie[m]*inv[n]%mod*inv[m-n]%mod;
    }
    void prepare()
    {
        jie[0]=inv[0]=1;
        for (int i=1;i<=MAX*2;i++) jie[i]=1ll*jie[i-1]*i%mod;
        for (int i=1;i<=MAX*2;i++) inv[i]=1ll*inv[i-1]*power(i,mod-2)%mod; 
    }
    int main()
    {
        scanf("%d%d%d%d%d",&n,&m,&r,&c,&s);
        MAX=max(max(n,m),max(r,c));
        prepare();
        int ans=0;
        for (int i=0;i<=min(n,r);i++)//行的贡献 
        {
            
            if (i*2==n) {
                if ((r-i)&1||s!=1ll*n*m/2) continue;
                ans=(ans+1ll*C(n,i)*C((r-i)/2+n-1,n-1)%mod*C(c+m-1,m-1)%mod)%mod;    
            }
            else 
            {    
                if ((s-1ll*i*m)%(n-2*i)!=0) continue;
                int j=(s-1ll*i*m)/(n-2*i);//列的贡献 
                if ((r-i)&1||(c-j)&1||j<0||j>c) continue;
                ans=(ans+1ll*C(n,i)*C(m,j)%mod*C((r-i)/2+n-1,n-1)%mod*C((c-j)/2+m-1,m-1)%mod)%mod;
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    idea
    Docker
    建张表
    MySQL 时间字段默认值
    获取唯一字符串
    Java避免空指针
    Android自定义控件
    Android 中textview显示富文本信息
    apktool反编译详细使用教程
    【android动态布局】之【ListView动态加载数据模板(使用xml布局)】
  • 原文地址:https://www.cnblogs.com/xxzh/p/9290218.html
Copyright © 2020-2023  润新知