CCPC 2016 G Marriage(HDU6270) 组合数学:容斥原理+NTT+启发式合并

本文详细介绍了如何利用组合数学的容斥原理和快速傅里叶变换(NTT)解决长春CCPC中的一道题目,该题目涉及在保持男女比例和避免近亲结婚的前提下,计算不同婚配方法的问题。作者通过建立生成函数,乘法运算和启发式合并来求解,并分享了在实现过程中遇到的错误和解决方法。此外,文章还提及了即将举行的JSCPC比赛。
摘要由CSDN通过智能技术生成

CCPC 2016 G Marriage(HDU6270) 组合数学:容斥原理+NTT+启发式合并

呜呜呜,为什么我长春CCPC,知乎上出题人的题解都咕着,我写了I的题解,结果没人看呢,PTA只有4A,不会是都不补题吧T_T(其实是百度不到我)。本来想蹭一波阅读量,大失败。

咳咳,回到正题。
题目意思是,一个城市里总共有N个家庭,第i个家庭有ai个男孩和bi个女孩,已知城市里男女比例1:1,在不近亲结婚且异性恋情况下,共有多少种不同的婚配方法(有一个人配偶不同即不同)
对于每个家庭,我们可以得到至少有i对近亲配对的方法。从而得到一个生成函数。对于这n个多项式,我们乘起来就得到了,城市里至少有i对近亲结婚的方法。我们可以用容斥原理计算,这里需要注意的是,不要忘记了给剩下的人配对,即总共有N对。对至少有i对近亲结婚的方法,要乘上(N-i)!

一开始WA了好多发,后来发现。。。我优先队列没pop干净,不过我塞了n个,每次取两个再塞一个进行n-1次,为什么我只pop一个没pop干净呢?疑惑。。。
代码如下:

#include <bits/stdc++.h>
using namespace std;
const long long MOD=998244353;
const long long g=3;
vector <long long> c[300010];
long long x1[300010],x2[300010],p[300010],inv[300010],invp[300010],rev[200010],t,sum,a[100010],b[100010];
long long ksm(long long x,long long y)
{
	long long res=x;
	long long ans=1;
	while (y)
	{
		if (y&1) ans=ans*res%MOD;
		res=res*res%MOD;
		y=y>>1;
	}
	return ans;
}
struct cmp
{
    bool operator()(int x, int y) 
	{
        return c[x].size()>c[y].size();
    }
};priority_queue <long long,vector<long long>,cmp> q;
void init()
{
	p[0]=1;
	for (int i=1;i<=300000;i++)
	{
		p[i]=p[i-1]*i%MOD;
	}
	inv[1]=1;
	for(int i=2;i<=300000;i++)
	{
		inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
	}
	invp[0]=1;
	invp[1]=1;
	for (int i=2;i<=300000;i++)
	{
		invp[i]=invp[i-1]*inv[i]%MOD;
	}
}
long long C(long long x,long long y)
{
	return (p[x]*invp[y]%MOD)*invp[x-y]%MOD;
}
inline void NTT(long long *z,long long len,long long invv) 
{ 
	long long bit=0; 
	while ((1<<bit)<len) 
	{ 
		++bit; 
	} 
	for(int i=0;i<=len-1;i++) 
	{ 
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1)); 
		if (i<rev[i])swap(z[i],z[rev[i]]); 
	} 
	for (int mid=1;mid<len;mid*=2) 
	{ 
		long long tmp=ksm(g,(MOD-1)/(mid*2)); 
		if (invv==-1) tmp=ksm(tmp,MOD-2); 
		for (int i=0;i<len;i+=mid*2) 
		{ 	
			long long omega=1; 
			for (long long j=0;j<mid;++j,omega=omega*tmp%MOD) 
			{ 
				long long x=z[i+j],y=omega*z[i+j+mid]%MOD; 		
				z[i+j]=(x+y)%MOD;
				z[i+j+mid]=(x-y+MOD)%MOD; 
			} 
		} 
	} 
	if (invv==-1) 
	for (int i=0;i<len;i++)
	{
		z[i]=z[i]*inv[len]%MOD;
	}
}
void mul(vector<long long> &x,vector<long long> &y,vector<long long> &z)
{
    long long len=1; 
	while (len<x.size()+y.size()) 
	{ 
		len=len<<1;
	} 
	for (int i=0;i<x.size();i++)
	{
		x1[i]=x[i];
	}
	for (int i=x.size();i<len;i++)
	{
		x1[i]=0;	
	}
	for (int i=0;i<y.size();i++)
	{
		x2[i]=y[i];
	}
	for (int i=y.size();i<len;i++)
	{
		x2[i]=0;	
	}
	NTT(x1,len,1);
	NTT(x2,len,1);
	for (int i=0;i<len;i++)
	{
		x1[i]=x1[i]*x2[i]%MOD;
	}
	NTT(x1,len,-1);
	for (int i=0;i<x.size()+y.size()-1;i++)
	{
		z.push_back(x1[i]);
	}
}
int main()
{
	long long t,n,i,j,flag,ans;
	init();
	scanf("%lld",&t);
	while (t--)
	{
		scanf("%lld",&n);
		for (i=0;i<=n*2;i++)
		{
			c[i].clear();
		}
		sum=0;
		for (i=1;i<=n;i++)
		{
			scanf("%lld%lld",&a[i],&b[i]);
			sum=sum+a[i];
			for (j=0;j<=b[i]&&j<=a[i];j++)
			{
				c[i].push_back((C(a[i],j)*C(b[i],j)%MOD)*p[j]%MOD);
			}
			q.push(i);
		}
		for (i=n+1;i<n*2;i++)
		{ 
			int x=q.top();q.pop();
			int y=q.top();q.pop();
			mul(c[x],c[y],c[i]);
			q.push(i);
		}
		while (!q.empty()) q.pop();
		flag=1;
		ans=0;
		for (i=0;i<c[n*2-1].size();i++)
		{
			ans=ans+flag*p[sum-i]*c[n*2-1][i]%MOD;
			ans=(ans+MOD)%MOD;
			flag=0-flag;
		}
		ans=(ans+MOD)%MOD;
		printf("%lld\n",ans);
	}
	return 0;
}

后记:明天JSCPC啦,早睡早睡。疫情之下,第一场线下赛。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值