分享

NOIP训练营内部试题-棋盘问题

 长沙7喜 2019-03-17

题目


#include<cstdio>

#include<algorithm>

using namespace std;

#define N 200001

typedef long long LL;

const int mod=1e9+7;

LL inv[N],fac[N];

LL pow(LL a,int b)

{

    LL res=1;

    for(;b;a=a*a%mod,b>>=1)

        if(b&1) res*=a,res%=mod;

    return res;

}

int main()

{

    freopen('c.in','r',stdin);

    freopen('c.out','w',stdout);

    int n,m,r,c; LL s;

    scanf('%d%d%d%d%I64d',&n,&m,&r,&c,&s);

    fac[0]=1;inv[0]=1;

    for(int i=1;i<=max(n,m)+max(r,c);i++) fac[i]=i*fac[i-1],fac[i]%=mod,inv[i]=pow(fac[i],mod-2);

    int j;

    int ans=0; LL res; 

    for(int i=(r&1);i<=min(r,n);i+=2)

    {

        if(n!=2*i)

        {

            if((s-1ll*i*m)%(n-2*i)) continue;

            j=(s-1ll*i*m)/(n-2*i);

            if(j<0 || j>min(c,m) || (c-j)&1) continue;

            res=fac[n]*inv[i]%mod*inv[n-i]%mod;

            res=res*fac[m]%mod*inv[j]%mod*inv[m-j]%mod;

            res=res*fac[n+(r-i>>1)-1]%mod*inv[n-1]%mod*inv[r-i>>1]%mod;

            res=res*fac[m+(c-j>>1)-1]%mod*inv[m-1]%mod*inv[c-j>>1]%mod;

            ans+=res; ans%=mod; 

            //printf('%d %d %I64d\n',i,j,res);

        }

    }

    printf('%d',ans);

}


解析:

解决本题关键:
同一行/列只有被选中奇数次才有效

假设有i行j列被翻了过来

那么可以得到等式

i*m+j*n-2*i*j=s

解得j=(s-i*m)/(n-2*i)

由此可知,我们只需要枚举i,就可以直接算出j

这个 i,j 合法的条件是:

① 不越界

②(n-i)%2=0,(m-j)%2=0

因为只能再翻偶数次,才能保证当前i,j 合法

如何计算一对合法的i,j的答案?

n行里i行被翻了过来  C(n,i)

m列里j列被翻了过来 C(m,j)

被翻了偶数次的行,就是把(r-i)/2  次机会 分给 n 行 C((r-i)/2+n-1,n-1)

注意不是n行里面选 (r-i)/2  行翻过来,因为同一行可以不翻,也可以翻多次

同理,偶数次列为 C((c-i)/2+m-1,m-1)

把这4个C 乘起来就是这一对i,j 的答案

最后累加所有的i,j 的贡献即可

本文解析作者:xxy

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多