hdu6036 Division Game 容斥+组合数学+NTT

28 篇文章 0 订阅
13 篇文章 0 订阅

Description


有0~k-1共k束花,每一束花中有m种颜色的花,第i种颜色有e[i]朵
第x次操作将会从第x%k束花中摘走至少一朵花,当一朵花被摘完游戏结束
对于i=0~k-1输出游戏在第i个位置恰好结束的方案数

Solution


每次至少摘一朵,那么游戏至多进行 n = ∑ i = 1 m e i n=\sum_{i=1}^{m}e_i n=i=1mei
f ( x ) f(x) f(x)表示在某个位置第x轮恰好结束的方案数,可以发现 f ( x ) f(x) f(x)同时也对应恰好在第x轮结束的方案数,我们钦定最后一次全拿完就可以了
于是第i个位置的答案就是 ∑ j = 0 n f ( j + 1 ) i − 1 f ( j ) k − j + 1 \sum\limits_{j=0}^{n}{f\left(j+1\right)}^{i-1}{f\left(j\right)}^{k-j+1} j=0nf(j+1)i1f(j)kj+1
考虑怎么求 f ( x ) f(x) f(x),我们可以看成是把x个球染上m种颜色分到k个盒子里面,要求每个盒子里不同颜色的球数量之和非零,而某种颜色的球在某个盒子里可以不放。
我们设 g ( x ) = ∏ i = 1 m ( e i + x − 1 x − 1 ) g(x)=\prod\limits_{i=1}^{m}\binom{e_i+x-1}{x-1} g(x)=i=1m(x1ei+x1),那么 f ( x ) f(x) f(x)就可以枚举空的位置容斥求了。于是有 f ( x ) = ∑ y = 0 x ( − 1 ) x − y ( x y ) g ( y ) f(x)=\sum\limits_{y=0}^{x}{{\left(-1\right)}^{x-y}\binom{x}{y}g(y)} f(x)=y=0x(1)xy(yx)g(y)

Code


#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>

#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const int MOD=985661441;
const int N=2000005;

int rv[N],p[N],e[N];
LL fac[N],inv[N],f[N],g[N];

void upd(LL &x,LL v) {
	x+=v,(x>=MOD)?(x-=MOD):0;
}

LL ksm(LL x,LL dep) {
	LL res=1;
	for (;dep;dep>>=1,x=x*x%MOD) {
		(dep&1)?(res=res*x%MOD):0;
	}
	return res;
}

void NTT(LL *a,int n,int f) {
	for (int i=0;i<n;++i) if (i<rv[i]) std:: swap(a[i],a[rv[i]]);
	for (int i=1;i<n;i<<=1) {
		LL wn=ksm(3,(f==1)?((MOD-1)/i/2):(MOD-1-(MOD-1)/i/2));
		for (int j=0;j<n;j+=(i<<1)) {
			LL w=1;
			for (int k=0;k<i;++k,w=w*wn%MOD) {
				LL u=a[j+k],v=a[j+k+i]*w%MOD;
				upd(a[j+k],v),a[j+k+i]=MOD-v;
				upd(a[j+k+i],u);
			}
		}
	}
	if (f==-1) {
		LL ny=ksm(n,MOD-2);
		for (int i=0;i<n;++i) a[i]=a[i]*ny%MOD;
	}
}

LL C(int n,int m) {
	if (m>n||m<0) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}

int main(void) {
	freopen("data.in","r",stdin);
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	rep(i,2,N-1) {
		fac[i]=fac[i-1]*i%MOD;
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	}
	rep(i,2,N-1) inv[i]=inv[i-1]*inv[i]%MOD;
	int n,m,k,T=0;
	while (~scanf("%d%d",&m,&k)) { n=0;
		rep(i,1,m) {scanf("%d%d",&p[i],&e[i]),n+=e[i];}
		int len=1,lg=0;
		for (;len<=n*2;) len<<=1,++lg;
		for (int i=0;i<len;++i) rv[i]=(rv[i>>1]>>1)|((i&1)<<(lg-1));
		for (int i=0;i<len;++i) f[i]=g[i]=0;
		rep(i,0,n) g[i]=1;
		rep(i,0,m) rep(j,0,n) g[j]=g[j]*C(e[i]+j-1,j-1)%MOD;
		rep(i,0,n) {
			f[i]=inv[i]; (i&1)?(f[i]=MOD-f[i]):0;
			g[i]=g[i]*inv[i]%MOD;
		}
		NTT(f,len,1),NTT(g,len,1);
		for (int i=0;i<len;++i) f[i]=f[i]*g[i]%MOD;
		NTT(f,len,-1);
		rep(i,0,n) f[i]=f[i]*fac[i]%MOD; f[n+1]=0;
		printf("Case #%d: ", ++T);
		rep(i,1,k) {
			LL ans=0;
			rep(j,0,n) upd(ans,ksm(f[j+1],i-1)*ksm(f[j],k-i+1)%MOD);
			if (i==k) printf("%lld\n", ans);
			else printf("%lld ", ans);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值