【NOI2013模拟】棋盘游戏

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)(每一次合并操作其实就是两次翻转)
.
.
.
.
.
.

程序:
#include<iostream>
using namespace std;

int mod=1000000007;
int n,m,r,c,max1;
long long  s;
int jie[200001],inv[200001];

int work(int x,int y)
{
    int ans=1;
    for (;y;y=y/2,x=1ll*x*x%mod)
    if (y&1) ans=1ll*ans*x%mod;
    return ans;
}
int h(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 jb()
{
    jie[0]=inv[0]=1;
    for (int i=1;i<=max1*2;i++) 
    jie[i]=1ll*jie[i-1]*i%mod;
    for (int i=1;i<=max1*2;i++) 
    inv[i]=1ll*inv[i-1]*work(i,mod-2)%mod; 
}
int main()
{
    int ans=0;
    cin>>n>>m>>r>>c>>s;
    max1=max(max(n,m),max(r,c));
    jb();
    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*h(n,i)*h((r-i)/2+n-1,n-1)%mod*h(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*h(n,i)*h(m,j)%mod*h((r-i)/2+n-1,n-1)%mod*h((c-j)/2+m-1,m-1)%mod)%mod;
        }
    }
    cout<<ans;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sslgz_yyc/article/details/80990752