【组合数学】石门夏令营0719测试 网络

题意

有一个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的数据不算特别大,可以写\Theta (nhw)或者\Theta (nrc)的做法。

于是我们分别用2个循环枚举行操作的有效个数和列操作的有效个数(此处的“有效个数”指对同一列没有做重复的操作),从而计算1的个数。

                                                                                                                                 图1

如图1所示,枚举行有效操作j次、列有效操作k次,那么总共动过了j\times w+k\times h个,但是有重复部分,于是我们要减去2次重复部分(一共动过重复部分2层,两层都要减去,使其变为0,不难发现有j\cdot k个),所以1的个数tar的通项公式tar=jw+kh-2jk

tar=s时,说明1的个数满足要求。但是可能会有“冗余操作”——j次行操作后尚未完成r次要求的行操作或者k次行操作后尚未完成c次要求的行操作,这是我们可以发现:

1. 循环枚举次数应该取j\rightarrow min(h,r)k\rightarrow min(w,c)

2. 冗余操作应该以2次为一个周期作用到同一行或同一列上,从而使1的个数不变。

这个时候我们就要分配冗余操作了:此时还有r-j次行操作和c-k次列操作尚未完成,分别有\frac{r-j}{2}\frac{c-k}{2}个周期(那么尚未完成的冗余操作次数都应该是偶数,否则无法维持1的个数)。此时动用一些组合数学方面的知识,运用“插板法”,可以得出将冗余行操作分配到h行中的分配数有个,同理可得冗余行操作分配到w列中的分配数有个。最后分配有效操作到h行w列上,所以做j次有效行操作、k次有效列操作的总方案数(计算冗余操作)共有个。

由于C++中没有计算组合数的公式,所以我们需要自行计算。众所周知C_{n}^{m}=\frac{A_{n}^{m}}{A_{n}^{n}},而,答案的数据非常大,那么分母A_{n}^{n}也会非常大,对A_{n}^{n}取余?但是基础的同余定理中没有“可除性”这个性质,因此我们要用另外一种方法——杨辉三角法。在组合数的“组合恒等式”中有C_{n}^{m}=C_{n-1}^{m-1}+C_{n-1}^{m}(本质上就是一个杨辉三角),因此可以用数组预处理组合数值。

诸如:

	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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值