莫名打不开这道题的链接,请读者自行搜索
其实题目里面已经给了提示了,对于行和列操作,顺序什么的是无所谓的,也就是说我们只需要考虑最终有几行被翻转了奇数次,几列被翻转了奇数次就可以统计答案了。
我们设有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; }