#组合,容斥#JZOJ 3332 棋盘游戏

比赛

题目

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


分析

细节太多,要慢慢讲。
首先要知道条件,设有X行,Y列有效取反,那么 S = X N + Y M 2 X Y 才会成立。
所以其实可以只枚举 X ,就可以求出 Y = S X N M 2 X
首先对于特殊情况分母为0时,对原式化简得 S = X N
S = M N / 2 ( M 2 X = 0 X 2 = M )
所以当分母为0且 S = M N / 2 X 2 = M 时Y=0
否则当 S X N M 2 X = S X N M 2 X 时就可以计算出Y
Y 0 , C Y 0 , C Y mod 2 = 0 ,那怎么算呢。
a n s + = C ( n , x ) C ( m , y ) C ( ( r x ) / 2 + n 1 , n 1 ) C ( ( c y ) / 2 + m 1 , m 1 )
前面好理解,n行选择x行的组合方案*m行选择y行的组合方案,后面是处理重复的取反,为
( r x ) / 2 ( c y ) / 2 无效取反,可以用隔板法,把无效取反放入隔板有多少种方案,给无效取反加上n-1(m-1)是为了留出空间。


代码

#include <cstdio>
#define mod 1000000007
#define bll long long
using namespace std;
bll n,m,r,c,f[150001],ans,s;
bll ksm(bll x,bll y){//快速幂
    bll ans=1;
    while (y){
        if (y&1) ans=(ans*x)%mod;
        x=(x*x)%mod; y>>=1;
    }
    return ans;
}
bll C(bll n,bll m){
    bll a=f[n],b=1ll*f[m]*f[n-m]%mod;
    return a*ksm(b,mod-2)%mod;//乘法逆元
}
int main(){
    scanf("%lld%lld%lld%lld%lld",&n,&m,&r,&c,&s); f[0]=f[1]=1ll;
    for (int i=1;i<=150000;i++) f[i]=f[i-1]*i%mod;//求阶乘
    for (int x=r%2;x<=r;x+=2){
        if ((x<<1)==n){//分母为0
            if (s<<1==n*m&&c%2==0) ans=(ans+(C(n,x)*C(m,0)%mod*C(((r-x)>>1)+n-1,n-1)*C((c>>1)+m-1,m-1)%mod)%mod)%mod;//如分析所示
        }
        else if ((s-x*m)%(n-(x<<1))==0){
            long long y=(s-x*m)/(n-(x<<1));//求出y
            if(y<0||c-y<0||(c-y)&1) continue;//不满足条件
            ans=(ans+(C(n,x)*C(m,y)%mod*C(((r-x)>>1)+n-1,n-1)%mod*C(((c-y)>>1)+m-1,m-1)%mod)%mod)%mod;//计算答案
        }
    }
    return !printf("%lld",ans);
}

猜你喜欢

转载自blog.csdn.net/sugar_free_mint/article/details/80993269
今日推荐