生成函数学习笔记
生成函数学习笔记
终于还是逃不过要动这个东西了……自闭……
前置知识:形式幂级数、等比数列、导数、泰勒展开、微积分、广义二项式定理。
0.我为啥要学生成函数?
考纲范围内
生成函数通俗来说是一个序列的母函数,在 OI 中多用来解决组合计数有关的问题,并时常配合邪恶多项式一同食用。
然而多项式在考纲外,于是我摆烂了。
抛开多项式,生成函数推式子的部分还是可考且非常重要的,因此有学习的必要。
1. 普通生成函数 \mathrm{Ordinary\ Generating\ Function}
形如
例 1:有
事实上,只有左边收敛的时候式子成立,但这不重要,因为对于形式幂级数我们只关心系数而不关心自变量的值到底是多少。
来一个简单的例题:化简
设
那么
两式相减,得到
因此
再来一个稍难一点的:求斐波那契数列的生成函数的封闭形式。
还是如法炮制:设
那么
根据定义不难得到
所以
上面的东西看起来很炫酷,可是有什么用呢?
回到上面斐波那契数列的生成函数,假设现在我们想求它的通项公式,怎么办?
考虑到其实我们是知道形如
直接因式分解,可以得到
然后我们要把这个函数变成若干个
代入原式,得到
里面的两个封闭形式都是我们熟悉的格式了,直接换成无穷级数形式得到
所以斐波那契数列的通项公式
通过这个例子,相信大家对于生成函数封闭形式的作用会有一个直观的感受。
2.指数生成函数 \mathrm{Exponential\ Generating\ Function}
刚才的 OGF 主要用来解决组合有关的问题,EGF 则用来解决排列有关的问题。
形如
为什么说 EGF 可以很好的解决排列有关的问题呢?来一个例题。
例 2:有
考虑像例 1 一样,把若干个 EGF 乘起来,看看会发生什么。
显然对于第
我们回头看一眼多重集排列计数的公式
几乎一模一样,只差一个系数
因此我们算出 EGF 取对应系数乘上
说完了 EGF 的作用,我们再来讨论一下有关 EGF 封闭形式的问题。
先从最简单的看起:
把求和符号打开:
欸,这我熟啊,
那问题解决了,这个 EGF 的封闭形式就是
一个难一点的:
怎么消掉奇数次幂的项?自然联想到
直接泰勒展开:
感谢上帝,他正是我们想要的,那么我们只要二者相加就可以消掉奇数次幂的项了。
然后调整偶数次幂的系数,容易得到封闭形式为
再来看这样一个东西:
考虑这个卷积和 EGF 的联系,拆掉这个组合数:
所以
明显地,这是三个 EGF 之间的运算,即
这启示我们,事实上很多集合划分的计数可以与 EGF 扯上关系。
看完了普通的加减乘运算,我们来看看 EGF 的
从
考虑整一个
显然
欸,右面那个东西不就是个组合卷积吗,于是我们直接整出来
我们来继续化简,两边同时求导:
移项:
再对两边同时积分:
得到
然后我们就可以使用多项式
可惜我不会多项式
继续来看 EGF 的
设这个数量是
这个我会!直接枚举每条边是否存在,设这个东西是
然后我们来看
这个就好得多,至少我们可以比较轻松地找出一个递推式,考虑枚举
又是我们熟悉的组合卷积,直接换成 EGF,设
注意到事实上我们并不需要
我们重新设
移项得到
熟悉的形式,直接两边积分,就得到了最终结果
3. 一些例题
洛谷 P2000 拯救世界
非常板板的 OGF。
注意到两个阵独立,每种石头也独立,其实就是一个容量为
那么我们直接写出十种限制的生成函数,显然不超过
运算过程:
那么我们最后的答案就是
由于
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
#define int long long
const int G=3,mod=998244353;
inline int pw(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
const int invG=pw(G,mod-2);
int tr[1000001<<1],len,n,m,f[1000001<<1],g[1000001<<1],ans[1000001<<1],invn;
string s;
inline void NTT(int *f,bool flag)
{
for(register int i=0;i<n;++i)
if(i<tr[i])
swap(f[i],f[tr[i]]);
for(register int p=2;p<=n;p<<=1)
{
int len=p>>1,tg=pw(flag? G:invG,(mod-1)/p);
for(register int k=0;k<n;k+=p)
{
int buf=1;
for(register int l=k;l<k+len;++l)
{
int tt=buf*f[l+len]%mod;
f[l+len]=(f[l]+mod-tt)%mod;
f[l]=(f[l]+tt)%mod;
buf=buf*tg%mod;
}
}
}
}
inline void mul(int c)
{
for(int i=0;i<n;++i)
g[i]=0;
for(int i=0;i<len;++i)
g[i]=s[i]^48;
g[0]+=c;
NTT(f,1);
NTT(g,1);
for(int i=0;i<n;++i)
f[i]=f[i]*g[i]%mod;
NTT(f,0);
int k=0;
for(int i=0;i<n;++i)
{
f[i]=f[i]*invn%mod+k;
k=f[i]/10;
f[i]%=10;
}
}
signed main()
{
cin>>s;
reverse(s.begin(),s.end());
len=s.length();
f[0]=1;
for(m=len*5,n=1;n<=m;n<<=1);
invn=pw(n,mod-2);
for(register int i=0;i<n;++i)
tr[i]=(tr[i>>1]>>1)|((i&1)? n>>1:0);
for(int i=0;i<len;++i)
f[i]=s[i]^48;
++f[0];
for(int i=2;i<=4;++i)
mul(i);
for(int i=n-1;i;--i)
{
f[i-1]+=f[i]%24*10;
f[i]/=24;
}
f[0]/=24;
int k=0;
for(int i=0;i<n;++i)
{
f[i]+=k;
k=f[i]/10;
f[i]%=10;
}
while(!f[n])
--n;
for(int i=n;~i;--i)
putchar(f[i]+'0');
puts("");
return 0;
}
CF947E Perpetual Subtraction
orz rqy。
设
这样计算当然不够快,我们来优化一下。
设
观察后面的分式,我们注意到
这个式子不好,因为我们只会做积分下界是 0 的式子,而且除以
设
啊哈!两个不好看的地方一起消失了,这样子我们直接来看
重大的进展!但遗憾的是距离正解还有很长的路要走——因为我们求出的是
同时这个关系应该是双向的,因为我们首先要由
直接把我们定义的两个生成函数展开:
非常明了,
但是这样没法快速算,我们把组合数拆了:
差卷积!我们的问题已经解决了一半,只剩下怎样由
注意到这个关系式是二项式反演的形式,我们直接反演一波:
还是不好看,我们再来拆了组合数:
还是差卷积。至此问题解决,代码实现需要写一个 NTT,时间复杂度
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
const int G=3,mod=998244353;
inline int pw(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
const int invG=pw(G,mod-2);
int n,m,p[100001],A[200001<<1],B[200001<<1],tr[200001<<1],fac[200001<<1],inv[200001<<1],g[200001<<1];
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x;
}
inline void NTT(int *f,bool flag,int n)
{
for(register int i=0;i<n;++i)
if(i<tr[i])
swap(f[i],f[tr[i]]);
for(register int p=2;p<=n;p<<=1)
{
int len=p>>1,tg=pw(flag? G:invG,(mod-1)/p);
for(register int k=0;k<n;k+=p)
{
int buf=1;
for(register int l=k;l<k+len;++l)
{
int tt=buf*f[l+len]%mod;
f[l+len]=(f[l]+mod-tt)%mod;
f[l]=(f[l]+tt)%mod;
buf=buf*tg%mod;
}
}
}
if(!flag)
{
int Inv=pw(n,mod-2);
for(register int i=0;i<n;++i)
f[i]=f[i]*Inv%mod;
}
}
inline void solve(int *A,int *B,int n)
{
int N;
for(N=n<<1,n=1;n<=N;n<<=1);
for(register int i=0;i<n;++i)
tr[i]=(tr[i>>1]>>1)|((i&1)? n>>1:0);
NTT(A,1,n);
NTT(B,1,n);
for(register int i=0;i<n;++i)
A[i]=A[i]*B[i]%mod;
NTT(A,0,n);
N>>=1;
for(register int i=N;i<n;++i)
A[i]=B[i]=0;
}
signed main()
{
n=read()+1,m=read()%(mod-1);
fac[0]=inv[0]=1;
for(register int i=0;i<n;++i)
{
p[i]=read();
if(i)
{
fac[i]=fac[i-1]*i%mod;
inv[i]=pw(fac[i],mod-2);
}
}
for(register int i=0;i<n;++i)
{
A[n-i-1]=fac[i]*p[i]%mod;
B[i]=inv[i];
}
solve(A,B,n);
for(register int i=0;i<n;++i)
g[i]=inv[i]*A[n-i-1]%mod*pw(pw(i+1,m),mod-2)%mod;
for(register int i=0;i<n;++i)
{
A[n-i-1]=fac[i]*g[i]%mod;
B[i]=i&1? mod-inv[i]:inv[i];
}
solve(A,B,n);
for(register int i=0;i<n;++i)
printf("%lld ",A[n-i-1]*inv[i]%mod);
puts("");
return 0;
}