[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\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xxzh/p/9290218.html