【生成函数+多项式求逆】LGP5162 WD与积木

本文探讨了一道关于随机堆叠积木的数学问题,通过使用生成函数和多项式求逆技巧,从O(n^2)的DP算法优化至O(nlogn)的高效解法。

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

【题目】
原题地址
n n n块积木,给每块积木随机一个大小并标号,然后将相同大小的积木放在一层,再从大到小堆起来。我们只关心积木的相对大小,因此所有堆法等概率出现,求期望层数。 T , n ≤ 1 0 5 T,n\leq 10^5 T,n105

【解题思路】
从期望的定义入手,我们先考虑一个朴素的 DP \text{DP} DP:设 f i f_i fi表示有 i i i块积木时产生层数和, g i g_i gi表示 i i i块积木不同堆法的数量,答案就是 f n g n \frac {f_n} {g_n} gnfn,其中 f 0 = 0 , g 0 = 1 f_0=0,g_0=1 f0=0,g0=1。我们枚举第一层放几块积木可以得到转移:
g n = ∑ i = 1 n ( n i ) g n − i g_n=\sum_{i=1}^n {n\choose i} g_{n-i} gn=i=1n(in)gni
f n = ∑ i = 1 n ( n i ) ( f n − i + g n − i ) = g n + ∑ i = 1 n ( n i ) f n − i f_n=\sum_{i=1}^n {n\choose i} (f_{n-i}+g_{n-i})=g_n+\sum_{i=1}^n {n\choose i} f_{n-i} fn=i=1n(in)(fni+gni)=gn+i=1n(in)fni
这样做是 O ( n 2 ) O(n^2) O(n2)的,这里好像还有一个标号顺序的问题我们好像没有考虑,但由于方案和层数是一一对应的,所以相当于同时约掉了。

我们考虑一个更为正确的写法:不妨设
F ( x ) = ∑ i f i i ! x i , G ( x ) = ∑ i g i i ! x i , H ( x ) = ∑ i x i i ! F(x)=\sum_{i} \frac {f_i} {i!} x^i,G(x)=\sum_{i} \frac {g_i} {i!} x^i,H(x)=\sum_{i} \frac {x^i} {i!} F(x)=ii!fixi,G(x)=ii!gixi,H(x)=ii!xi
由于没有组合数的计算,我们这里写出的生成函数要考虑顺序(实际上和前几天的生成函数题类似,答案的形式都是 n ! k 1 ! k 2 ! … \frac {n!} {k_1!k_2!\dots} k1!k2!n!

我们可以得到
2 F ( x ) = G ( x ) + F ( x ) H ( x ) − 1 , 2 G ( x ) = G ( x ) H ( x ) + 1 2F(x)=G(x)+F(x)H(x)-1,2G(x)=G(x)H(x)+1 2F(x)=G(x)+F(x)H(x)1,2G(x)=G(x)H(x)+1
F ( x ) = G ( x ) ( G ( x ) − 1 ) , G ( x ) = 1 2 − H ( x ) F(x)=G(x)(G(x)-1),G(x)=\frac 1 {2-H(x)} F(x)=G(x)(G(x)1),G(x)=2H(x)1

于是多项式求逆可以做到 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

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

typedef long long ll;
const int N=1e5+5,M=264233;
const int mod=998244353,G=3;
int fac[M],ifac[M];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
int qpow(int x,int y)
{
	int res=1;
	for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) res=(ll)res*x%mod;
	return res;
}
int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
void up(int &x,int y){x=upm(x+y);}

namespace NTT
{
	int m,L;
	int f[M],g[M],h[M],t[M],t2[M];
	int rev[M];
	void reget(int n)
	{
		for(L=0,m=1;m<2*n;++L,m<<=1);
		for(int i=0;i<m;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
	}
	void ntt(int *a,int n,int op)
	{
		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)
		{
			int wn=qpow(G,(mod-1)/(i<<1));
			if(!~op) wn=qpow(wn,mod-2);
			for(int j=0;j<n;j+=(i<<1))
			{
				int w=1;
				for(int k=0;k<i;++k,w=(ll)w*wn%mod)
				{
					int x=a[j+k],y=(ll)w*a[i+j+k]%mod;
					a[j+k]=upm(x+y);a[i+j+k]=upm(x-y);
				}
			}
		}
		int inv=qpow(n,mod-2);
		if(!~op) for(int i=0;i<n;++i) a[i]=(ll)a[i]*inv%mod;
	}
	void polyinv(int *a,int *b,int deg)
	{
		if(deg==1){b[0]=qpow(a[0],mod-2);return;}
		polyinv(a,b,(deg+1)>>1);
		reget(deg);
		for(int i=0;i<deg;++i) t[i]=a[i];
		for(int i=deg;i<m;++i) t[i]=0;
		ntt(b,m,1);ntt(t,m,1);
		for(int i=0;i<m;++i) b[i]=(ll)(upm(2-(ll)b[i]*t[i]%mod))*b[i]%mod;
		ntt(b,m,-1);
		for(int i=deg;i<m;++i) b[i]=0;
	}
	void mul(int *a,int *b,int *c,int deg)
	{
		reget(deg);
		for(int i=0;i<deg;++i) t[i]=a[i],c[i]=b[i];
		for(int i=deg;i<m;++i) t[i]=c[i]=0;
		ntt(t,m,1);ntt(c,m,1);
		for(int i=0;i<m;++i) c[i]=(ll)t[i]*c[i]%mod;
		ntt(c,m,-1);
		for(int i=deg;i<m;++i) c[i]=0;
	}
}
using namespace NTT;

void init()
{
	fac[0]=1;for(int i=1;i<N;++i)fac[i]=(ll)fac[i-1]*i%mod; 
	for(int i=0;i<N;++i)ifac[i]=qpow(fac[i],mod-2);
	for(int i=0;i<N;++i) h[i]=mod-ifac[i]; h[0]=upm(h[0]+2);
	polyinv(h,g,N);
	for(int i=0;i<N;++i) f[i]=g[i]; f[0]=upm(g[0]-1);
	mul(f,g,f,N);
}
void solve()
{
	int T=read();
	while(T--)
	{
		int x=read();
		printf("%d\n",(ll)f[x]*qpow(g[x],mod-2)%mod);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LGP5162.in","r",stdin);
	freopen("LGP5162.out","w",stdout);
#endif
	init();solve();
	return 0;
}

【总结】
事实证明我的 DP \text{DP} DP学得并不好,开始的式子想了很久。
然而后面的生成函数一下子就搞出来了。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值