题意
有一个h行w列的网格,每个格子一开始都有一个数字0,有两种操作:
1、行操作,选中一行,0变1,1变0。
2、列操作,选择一列,0变1,1变0。
总共要进行r次行操作和c次列操作,目标是最后网格有s个1,有多少种不同的方案?答案模555555555。
同一行可以进行多次重复“行操作”,同一列可以进行多次“列操作”。
两种方案不同是指:存在某行或者某列,在两种方案中,操作的次数不同。
多组测试数据。
第一行,一个整数g,表示有g组测试数据,g<=5。每组测试数据格式:一行,5个整数,h,w,r,c,s。1<=h,w<=1555, 0<=r,c<=1555, 0<=s<=h*w。
思路
因为h和w的数据不算特别大,可以写或者
的做法。
于是我们分别用2个循环枚举行操作的有效个数和列操作的有效个数(此处的“有效个数”指对同一列没有做重复的操作),从而计算1的个数。
图1
如图1所示,枚举行有效操作j次、列有效操作k次,那么总共动过了个,但是有重复部分,于是我们要减去2次重复部分(一共动过重复部分2层,两层都要减去,使其变为0,不难发现有
个),所以1的个数tar的通项公式
。
当时,说明1的个数满足要求。但是可能会有“冗余操作”——j次行操作后尚未完成r次要求的行操作或者k次行操作后尚未完成c次要求的行操作,这是我们可以发现:
1. 循环枚举次数应该取和
。
2. 冗余操作应该以2次为一个周期作用到同一行或同一列上,从而使1的个数不变。
这个时候我们就要分配冗余操作了:此时还有次行操作和
次列操作尚未完成,分别有
和
个周期(那么尚未完成的冗余操作次数都应该是偶数,否则无法维持1的个数)。此时动用一些组合数学方面的知识,运用“插板法”,可以得出将冗余行操作分配到h行中的分配数有
个,同理可得冗余行操作分配到w列中的分配数有
个。最后分配有效操作到h行w列上,所以做j次有效行操作、k次有效列操作的总方案数(计算冗余操作)共有
个。
由于C++中没有计算组合数的公式,所以我们需要自行计算。众所周知,而,答案的数据非常大,那么分母
也会非常大,对
取余?但是基础的同余定理中没有“可除性”这个性质,因此我们要用另外一种方法——杨辉三角法。在组合数的“组合恒等式”中有
(本质上就是一个杨辉三角),因此可以用数组预处理组合数值。
诸如:
for(int i=0;i<3200;i++)
C[i][0]=1;//记得C(n,m)m=0时的性质——为1
for(int i=1;i<3200;i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
最后,题目说“答案模555555555”,记得随时取模!
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll z=3222,mod=555555555;
ll g,h,w,r,c,s,C[z][z],lxr,lxc,tar,ans;
void fastread()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}//快读
int main()
{
fastread();
for(int i=0;i<3200;i++)
C[i][0]=1;
for(int i=1;i<3200;i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;//组合数预处理
cin>>g;
for(int i=1;i<=g;i++)
{
cin>>h>>w>>r>>c>>s;
ans=0,lxr=min(h,r),lxc=min(w,c);//取最小值“理想情况”
for(int j=0;j<=lxr;j++)
{
for(int k=0;k<=lxc;k++)
{
tar=j*w+k*h-2*j*k;//计算1的个数
if(tar==s&&(r-j)%2==0&&(c-k)%2==0)
{
ans+=((C[h][j]*C[h+(r-j)/2-1][h-1]%mod)*C[w][k]%mod)*C[w+(c-k)/2-1][w-1]%mod;
ans%=mod;//记得模
}
}
}
cout<<ans<<endl;//记得换行
}
return 0;
}