【生成函数+容斥原理+NTT】HDU6036 Division Game

本文深入探讨了一道复杂的堆石子博弈问题,通过构造生成函数的方法,详细解析了问题的解题思路与算法实现。重点讲解了如何利用生成函数进行方案数计算,以及使用拆系数FFT或NTT优化计算过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目】
k k k堆石子,每堆石子初始数量均为 n n n,编号 0 0 0~ k − 1 k-1 k1,第 i i i次操作对第 ( i − 1 ) % k (i-1)\%k (i1)%k堆石子操作,可以从该堆石子中拿走若干石子(至少要拿走一颗),要求拿走后这堆石子的个数是拿走前这堆石子个数的一个约数。当某堆石子被取走若干石子后变成 1 1 1时结束操作。问最终操作结束于第 i i i堆的方案数 。答案对 985661441 985661441 985661441取模。
其中 n = ∏ i = 1 m p i e i , m , k ≤ 10 , 2 ≤ p i ≤ 1 0 9 , ∑ e i ≤ 1 0 5 , 1 ≤ e i n=\prod^m_{i=1}p_i^{e_i},m,k\leq 10,2\leq p_i \leq 10^9,\sum e_i\leq 10^5,1\leq e_i n=i=1mpieim,k10,2pi109,ei105,1ei
原题地址

【解题思路】
这个题很难啊qwq,反正我是没有想到的。那么下面引用官方题解

显然每个石子堆最多做 w = ∑ i = 1 m e i w=\sum_{i=1}^m e_i w=i=1mei次操作。
现在我们定义 f ( x ) f(x) f(x)为某堆石子做 x x x次操作后恰好变为 1 1 1个的生成函数。那么我们可以得到每个数字做少于 x x x次操作不变为 1 1 1的生成函数也是 f ( x ) f(x) f(x)。(这两个东西显然是一一对应的)

现在统计结束于第 i i i堆石子的情况,我们枚举它是第几次操作时结束的,不妨设为 x x x,那么对应的方案数就是 f ( x + 1 ) i − 1 f ( x ) k − i + 1 f(x+1)^{i-1}f(x)^{k-i+1} f(x+1)i1f(x)ki+1,这是因为编号 ≤ i \leq i i的石子堆在 x x x次操作后仍然没有变为 1 1 1,编号 ≥ i \geq i i的石子堆在 x − 1 x-1 x1次操作后没有变为 1 1 1,只有编号为 i i i的石子堆恰好变为 1 1 1
那么如果我们知道 f ( x ) f(x) f(x),则可以用 O ( w k ) O(wk) O(wk)的时间解决问题。

考虑 f ( x ) f(x) f(x)的组成,不妨设某种方案第 j j j次操作 e i e_i ei减少了 d ( i , j ) d(i,j) d(i,j),我们知道对于每个 e i e_i ei ∑ j = 1 x d ( i , j ) = e i \sum_{j = 1}^{x}{d(i, j)} = e_i j=1xd(i,j)=ei,且对于每个 j j j ∑ i = 1 m d ( i , j ) > 0 \sum_{i = 1}^{m}{d(i, j)} \gt 0 i=1md(i,j)>0
那么 f ( x ) f(x) f(x)就是分配 d ( i , j ) ( 1 ≤ i ≤ m , 1 ≤ j ≤ x ) d(i,j)(1 \leq i \leq m, 1 \leq j \leq x) d(i,j)(1im,1jx)使得它满足上面两个条件的方案数。

如果只考虑第一个条件对应的方案数 g ( x ) g(x) g(x),我们可以规约到一个经典的组合问题,求一个方程非负整数解的个数。
g ( x ) = ∏ i = 1 m ( e i + x − 1 x − 1 ) \displaystyle g(x) = \prod_{i = 1}^{m}{e_i + x - 1 \choose x - 1} g(x)=i=1m(x1ei+x1)

这个公式可以通过对 x − 1 x-1 x1个隔板, e i e_i ei个物品的排列数量得到,隔板把区间分成了 x x x块,每块区间中物品的数量对应一个变量,排列的数量等同于隔板选位置的方法数。

观察到如果某些 j j j与第二个条件产生了矛盾,与之相关的 d ( i , j ) d(i,j) d(i,j)都会是零。利用容斥原理可以得到
f ( x ) = ∑ y = 0 x ( − 1 ) x − y ( x y ) g ( y ) \displaystyle f(x) = \sum_{y = 0}^{x}{(-1)^{x - y} {x \choose y} g(y)} f(x)=y=0x(1)xy(yx)g(y)
可以发现:
f ( x ) x ! = ∑ y = 0 x ( − 1 ) x − y ( x − y ) ! ⋅ g ( y ) y ! \displaystyle \frac{f(x)}{x!} = \sum_{y = 0}^{x}{\frac{(-1)^{x - y}}{(x - y)!} \cdot \frac{g(y)}{y!}} x!f(x)=y=0x(xy)!(1)xyy!g(y)
拆 系 数 F F T 拆系数FFT FFT N T T NTT NTT即可。 ( 985661441 = 235 × 2 22 + 1 ) (985661441 = 235 \times 2^{22} + 1) (985661441=235×222+1)

总的时间复杂度是 O ( w log ⁡ n + w k ) O(w \log n + w k) O(wlogn+wk)

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=2e5+10;
const int mod=985661441,g=985661438;
int n,m,k,L,pris;
LL fac[N],ifac[N],f[N],e[N],rev[N<<1],a[N<<1],b[N<<1];

LL qpow(LL x,LL y){LL ret=1;for(;y;y>>=1,x=x*x%mod) if(y&1) ret=ret*x%mod;return ret;}
LL C(int n,int m){return fac[n]*ifac[m]%mod*ifac[n-m]%mod;}
void up(LL &x,LL y){x+=y;if(x>=mod)x-=mod;}

void totinit()
{
	fac[0]=fac[1]=1;
	for(int i=2;i<N;++i) fac[i]=fac[i-1]*i%mod;
	ifac[N-1]=qpow(fac[N-1],mod-2);
	for(int i=N-1;i;--i) ifac[i-1]=ifac[i]*i%mod;
}

void ntt(LL *a,int n,int f)
{
	for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int i=1;i<n;i<<=1)
	{
		LL wn=qpow(g,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 x=a[j+k],y=w*a[i+j+k]%mod;
				a[j+k]=(x+y)%mod;a[i+j+k]=(x-y+mod)%mod;
			}
		}
	}
	LL inv=qpow(n,mod-2);
	if(f==-1) for(int i=0;i<n;++i) a[i]=a[i]*inv%mod;
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("HDU6036.in","r",stdin);
	freopen("HDU6036.out","w",stdout);
#endif
	totinit();int cas=0;
	while(scanf("%d%d",&pris,&k)!=EOF)
	{
		++cas;printf("Case #%d: ",cas);n=0;
		for(int i=1,x;i<=pris;++i) scanf("%d%d",&x,&e[i]),n+=e[i];
		for(L=0,m=1;m<=n<<1;m<<=1,++L);
		for(int i=0;i<m;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1)),a[i]=b[i]=0;
		for(int i=0;i<=n;++i) a[i]=(i&1)?mod-ifac[i]:ifac[i];
		for(int i=1;i<=n;++i)
		{
			b[i]=ifac[i];
			for(int j=1;j<=pris;++j) b[i]=b[i]*C(e[j]+i-1,i-1)%mod;
		}
		ntt(a,m,1);ntt(b,m,1);
		for(int i=0;i<m;++i) a[i]=a[i]*b[i]%mod;
		ntt(a,m,-1);
		for(int i=0;i<=n;++i) f[i]=a[i]*fac[i]%mod; f[n+1]=0;
		for(int i=1;i<=k;++i)
		{
			LL ans=0;
			for(int j=1;j<=n;++j) up(ans,qpow(f[j],k-i+1)*qpow(f[j+1],i-1)%mod);
			if(i<k) printf("%lld ",ans); else printf("%lld\n",ans);
		}
	}

	return 0;
}

【总结】
论如何构造生成函数计算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值