====== 生成函数理论 5 ====== ===== 基本应用 ===== ==== 例 1 ==== [[https://www.luogu.com.cn/problem/P2000|P2000 拯救世界]] 依照题意可列出 10 个条件的生成函数 $f_i(x)$,将它们相乘再化简即可。 * $f_1(x)=(1+x^6+x^{12}+\cdots)=\frac{1}{1-x^6}$ * $f_2(x)=(1+x+x^2+\cdots+x^9)=\frac{1-x^{10}}{1-x}$ * $f_3(x)=(1+x+x^2+\cdots+x^5)=\frac{1-x^6}{1-x}$ * $f_4(x)=(1+x^4+x^8+\cdots)=\frac{1}{1-x^4}$ * $f_5(x)=(1+x+x^2+\cdots+x^7)=\frac{1-x^8}{1-x}$ * $f_6(x)=(1+x^2+x^4+\cdots)=\frac{1}{1-x^2}$ * $f_7(x)=(1+x)=\frac{1-x^2}{1-x}$ * $f_8(x)=(1+x^8+x^{16}+\cdots)=\frac{1}{1-x^8}$ * $f_9(x)=(1+x^{10}+x^{20}+\cdots)=\frac{1}{1-x^{10}}$ * $f_{10}(x)=(1+x+x^2+x^3)=\frac{1-x^4}{1-x}$ $\displaystyle f(x)=\prod_{i=1}^{10}f_i(x)=\frac{1}{(1-x)^5}=\sum_{n=0}^{\infty}\binom{n+4}{n}x^n$ $\displaystyle ans_n=\binom{n+4}{4}=\frac{(n+4)(n+3)(n+2)(n+1)}{24}$ 由于 $10^{100000}\le n<10^{100001}$,需要用高精度,正解是用 NTT,用 Python 会 TLE,但是竟然可以用 Ruby AC。 Ruby 代码: n=Integer(gets) puts (n+4)*(n+3)*(n+2)*(n+1)/24 C++ 代码: #include using namespace std; const int N=5e6+5,P=998244353,G=3,Gi=332748118; typedef long long ll; int rev[N]; char s[N]; ll n1[N],n2[N],n3[N],n4[N]; ll fastpow(ll x,ll y){ ll ret=1; for(;y;y>>=1,x=x*x%P) if(y&1) ret=ret*x%P; return ret; } void NTT(ll *y,int len,int on){ for(int i=0;i>1]>>1)|((i&1)<<(l-1)); NTT(a,len,1); NTT(b,len,1); for(int i=0;i=0;i--){ p=p*10+n1[i]; ans[i]=p/24; p%=24; if(ans[i]&&!lenans) lenans=i; } for(int i=lenans;i>=0;i--) printf("%lld",ans[i]); return 0; } ==== 例 2 (Bell 数) ==== 令 $B_n$ 表示 $[n]$ ($n$ 元集合)所有划分的个数,试找到计算 $B_n$ 的公式。 讨论 $[n]$ 的划分中包含元素 $n$ 的那个子集,设其含有 $k$ ($1\le k\le n$)个元素,则剩余的 $k-1$ 个元素是从 $[n-1]$ 中选取的。而剩余的那些子集为 $n-k$ 个元素的一个划分,所以 $$ B_n=\sum_{k=1}^{n}\binom{n-1}{k-1}B_{n-k},~~~~n\ge 1. $$ 初始值为 $B_0=1,B_1=1$。令 $\displaystyle B(x)=\sum_{n\ge 0}\frac{B_n}{n!}x^n$,两边求导数,得到 $$ \begin{align} \frac{dB(x)}{dx}&=\sum_{n=1}^{\infty}\frac{B_n}{(n-1)!}x^{n-1}\\ &=\sum_{n=1}^{\infty}\frac{1}{(n-1)!}\left(\sum_{k=1}^{n}\binom{n-1}{k-1}B_{n-k}\right)x^{n-1}\\ &=\sum_{n=1}^{\infty}\sum_{k=1}^{n}\frac{x^{k-1}}{(k-1)!}\frac{B_{n-k}x^{n-k}}{(n-k)!}\\ &=\sum_{k=1}^{\infty}\frac{x^{k-1}}{(k-1)!}\sum_{n\ge k}\frac{B_{n-k}x^{n-k}}{(n-k)!}\\ &=\sum_{k=1}^{\infty}\frac{x^{k-1}}{(k-1)!}\sum_{i\ge 0}\frac{B_ix^i}{i!}~~(i=n-k)\\ &=e^xB(x). \end{align} $$ 解微分方程 $\displaystyle \frac{dB(x)}{dx}=e^xB(x)$,可得 $$ B(x)=Ce^{e^x}, $$ 其中 $C$ 为待定常数。由初始条件 $B(0)=1$ 得到 $C=e^{-1}$,即 $$ B(x)=e^{e^x-1}, $$ 从而 $$ \begin{align} B(x)&=\frac{1}{e}\sum_{k=0}^{\infty}\frac{(e^x)^k}{k!}=\frac{1}{e}\sum_{k=0}^{\infty}\frac{1}{k!}\sum_{n=0}^{\infty}\frac{k^nx^n}{n!}\\ &=\frac{1}{e}\sum_{n=0}^{\infty}\left(\sum_{k=0}^{\infty}\frac{k^n}{k!}\right)\frac{x^n}{n!}. \end{align} $$ 因此 $$ B_n=\frac{1}{e}\sum_{k=0}^{\infty}\frac{k^n}{k!}. $$ $\Box$ ==== 例 3 ==== === 题意 === [[https://www.luogu.com.cn/problem/P4451|P4451 [国家集训队]整数的lqp拆分]] === 题解 === Fibonacci 数的生成函数是这个(基础知识): $$ F(x)=\frac{x}{1-x-x^2} $$ 当拆分为恰好 $k$ 个数时,答案的生成函数是 $F(x)^k$,那么答案的生成函数即为 $$ \begin{align} G(x)&=\sum_{i=0}^{\infty}F(x)^i\\ &=\frac{1}{1-F(x)}\\ &=\frac{1-x-x^2}{1-2x-x^2}\\ &=1+\frac{x}{1-2x-x^2}\\ &=1+\frac{\sqrt 2}{4}(\frac{1}{1-(1+\sqrt 2)x}-\frac{1}{1-(1-\sqrt 2)x})\\ &=1+\frac{\sqrt 2}{4}\sum_{n=0}^{\infty}[(1+\sqrt 2)^n-(1-\sqrt 2)^n]x^n \end{align} $$ 所以通项公式为 $$ a_n\equiv \frac{\sqrt 2}{4}[(1+\sqrt 2)^n-(1-\sqrt 2)^n]\pmod{1e9+7} $$ 需要用二次剩余解出 $x^2\equiv 2\pmod{1e9+7}$ === 评价 === 对于理解生成函数和公式推导有一定的帮助 === 代码 === #include using namespace std; typedef long long ll; const ll mod=1e9+7; const ll two=59713600; ll ksm(ll x,ll y){ ll ret=1; for(;y;y>>=1,x=x*x%mod) if(y&1) ret=ret*x%mod; return ret; } int main(){ string s; cin>>s; ll n=0; int len=s.length(); for(int i=0;i === 拓展 === 令 $a_n$ 表示在一个 n-集合上完成某个任务的方法数,$a_0=0$。对于 $n\ge 1$,令 $b_n$ 表示将集合 $[n]$ 划分成任意的连续、非空区间段,然后在这些非空区间段上完成前面任务的方法数,$b_0=1$。设 $\{a_n\}_{n=0}^{\infty}$ 和 $\{b_n\}_{n=0}^{\infty}$ 的普通生成函数分别为 $f(x)$ 和 $g(x)$,则对任意固定的正整数 $i$,将 $[n]$ 划分成 $i$ 个非空子区间时,所对应的方法数的普通生成函数为 $f^i(x)$,且有 $$ g(x)=\frac{1}{1-f(x)}. $$ ==== 例 4 ==== === 题意 === [[https://www.luogu.com.cn/problem/P4841|P4841 [集训队作业2013]城市规划]] 求出 $n$ 个点的简单(无重边无自环)有标号无向连通图数目。 === 题解 === 令 $\{a_n\}_{n=0}^{\infty}$ 为答案,$\{b_n\}_{n=0}^{\infty}$ 为 $n$ 个点的无向图的数目,显然 $b_n=2^{\binom{n}{2}}$,设 $1$ 号点所在连通块大小为 $k$,则有 $$ \begin{align} b_n&=\sum_{k=1}^{n}\binom{n-1}{k-1}a_kb_{n-k}\\ &=\sum_{k=0}^{n-1}\binom{n-1}{k}a_{k+1}b_{n-k-1}\\ b_{n+1}&=\sum_{k=0}^{n}\binom{n}{k}a_{k+1}b_{n-k} \end{align} $$ 令 $c_k=a_{k+1}$,$A(x),B(x),C(x)$ 分别为 $\{a_n\}_{n=0}^{\infty},\{b_n\}_{n=0}^{\infty},\{c_n\}_{n=0}^{\infty}$ 的指数型生成函数,则 $$ b_{n+1}=\sum_{k=0}^n\binom{n}{k}c_kb_{n-k} $$ 从而 $$ B'(x)=C(x)B(x)=A'(x)B(x) $$ 解得 $$ A(x)=\ln B(x) $$ 所以套用多项式求对数的模板即可求解 $a_n$。 === 代码 === #include #define _for(i,a,b) for(int i=(a);i<(b);++i) #define _rep(i,a,b) for(int i=(a);i<=(b);++i) using namespace std; const int MAXN=1.3e5+5,Mod=1004535809; int n; int a[MAXN<<2],b[MAXN<<2]; int quick_pow(int x,int y){ int ret=1; for(;y;y>>=1,x=1ll*x*x%Mod) if(y&1) ret=1ll*ret*x%Mod; return ret; } namespace Poly{ const int G=3; int rev[MAXN<<2],Pool[MAXN<<3],*Wn[30]; void init(){ int lg2=0,*pos=Pool,n,w; while((1<>=1; } } int build(int k){ int n,pos=0; while((1<>1]>>1)|((i&1)<<(pos-1)); return n; } void NTT(int *f,int n,bool type){ _for(i,0,n)if(i>1); int n=build((_n-1)<<1); _for(i,(_n+1)>>1,n)g[i]=0; _for(i,0,_n)temp[i]=f[i];_for(i,_n,n)temp[i]=0; NTT(g,n,true);NTT(temp,n,true); _for(i,0,n)g[i]=(2-1LL*temp[i]*g[i]%Mod)*g[i]%Mod; NTT(g,n,false); _for(i,_n,n)g[i]=0; } void Div(const int *f,int _n,const int *g,int _m,int *q,int *r){ static int temp[MAXN<<2]; if(_n<_m){ _rep(i,0,n)r[i]=f[i]; return; } _rep(i,0,_m)temp[i]=g[_m-i]; Inv(temp,q,_n-_m+1); _rep(i,0,_n)temp[i]=f[_n-i]; Mul(q,_n-_m+1,temp,_n+1,_n-_m+1); for(int i=0,j=_n-_m;i>1); _for(i,(_n+1)>>1,_n)g[i]=0; Ln(g,temp,_n); temp[0]=(1+f[0]-temp[0])%Mod; _for(i,1,_n)temp[i]=(f[i]-temp[i])%Mod; Mul(g,(_n+1)>>1,temp,_n,_n); } void Pow(const int *f,int *g,int _n,int k1,int k2){ static int temp[MAXN<<2]; int pos=0,posv; while(!f[pos]&&pos<_n)pos++; if(1LL*pos*k2>=_n){ _for(i,0,_n)g[i]=0; return; } posv=quick_pow(f[pos],Mod-2); _for(i,pos,_n)g[i-pos]=1LL*f[i]*posv%Mod; _for(i,_n-pos,_n)g[i]=0; Ln(g,temp,_n); _for(i,0,_n)temp[i]=1LL*temp[i]*k1%Mod; Exp(temp,g,_n); pos=pos*k2,posv=quick_pow(posv,1LL*k2*(Mod-2)%(Mod-1)); for(int i=_n-1;i>=pos;i--)g[i]=1LL*g[i-pos]*posv%Mod; _for(i,0,pos)g[i]=0; } } int fact[MAXN<<2]; int invfact[MAXN<<2]; int main(){ scanf("%d",&n); b[0]=1; fact[0]=1; for(int i=1;i<=n;i++){ fact[i]=1ll*fact[i-1]*i%Mod; } invfact[n]=quick_pow(fact[n],Mod-2); for(int i=n;i>=1;i--) invfact[i-1]=1ll*invfact[i]*i%Mod; for(int i=0;i<=n;i++) b[i]=1ll*quick_pow(2,1ll*i*(i-1)/2%(Mod-1))*invfact[i]%Mod; Poly::init(); Poly::Ln(b,a,n+1); printf("%d",1ll*a[n]*fact[n]%Mod); return 0; } === 拓展 === 令 $a_n$ 表示在一个 n-集合上完成某个任务的方法数,$a_0=0$。对于 $n\ge 1$,令 $b_n$ 表示将集合 $[n]$ 划分成任意的非空子集,然后在这些非空子集上完成前面任务的方法数,$b_0=1$。设 $\{a_n\}_{n=0}^{\infty}$ 和 $\{b_n\}_{n=0}^{\infty}$ 的指数型生成函数分别为 $f(x)$ 和 $g(x)$,则对任意固定的正整数 $i$,将 $[n]$ 划分成 $i$ 个非空子集时,所对应的方法数的指数型生成函数为 $f^i(x)/i!$,且有 $$ g(x)=e^{f(x)}. $$ ==== 例 5 ==== === 题意 === [[https://www.luogu.com.cn/problem/CF438E|CF438E The Child and Binary Tree]] 给定一个含有 $n$ 个互异正整数的数列 $c_1,c_2,\cdots,c_n$,如果一棵带点权的有根二叉树满足其所有顶点的权值都在集合 $\{c_1,c_2,\cdots,c_n\}$ 中,则称之为好树,一棵带点权的树的权值是其所有顶点权值的总和。给出一个正整数 $m$,对任意的 $s,1\leq s\leq m$,计算出权值为 $s$ 的好树个数。 === 题解 === 设 $f_i$ 表示点权和为 $i$ 的符合条件的二叉树的个数,$g_i$ 表示权值 $i$ 是否在 $c_i$ 中。 $$ f_0=1\\ f_n=\sum_{i=1}^ng_i\sum_{j=1}^{n-i}f_jf_{n-i-j}~~(n>0) $$ 这个式子的意思是枚举根结点的权值 $i$,然后枚举根结点左右子树的权值,容易发现这是三个多项式的卷积。用 $F(x),G(x)$ 分别表示数列 $\{f_n\}_{n=0}^{\infty},\{g_n\}_{n=0}^{\infty}$ 的普通生成函数。则有 $$ F(x)=G(x)F^2(x)+1 $$ 解得 $$ F(x)=\frac{2}{1+\sqrt{1-4G(x)}} $$ 所以套用多项式开根+求逆即可 === 代码 === #include #define _for(i,a,b) for(int i=(a);i<(b);++i) #define _rep(i,a,b) for(int i=(a);i<=(b);++i) using namespace std; const int MAXN=1e5+5,Mod=998244353; int n; int g[MAXN<<2],f[MAXN<<2]; int h[MAXN<<2]; int quick_pow(int x,int y){ int ret=1; for(;y;y>>=1,x=1ll*x*x%Mod) if(y&1) ret=1ll*ret*x%Mod; return ret; } unordered_mapH; int bsgs(int a,int b){ H.clear(); int m=sqrt(Mod)+1,t=b,base; for(int i=1;i<=m;i++){ t=1ll*t*a%Mod; H[t]=i; } t=1,base=quick_pow(a,m); for(int i=1;i<=m;i++){ t=1ll*t*base%Mod; if(H[t]) return m*i-H[t]; } return -1; } namespace Poly{ const int G=3; int rev[MAXN<<2],Pool[MAXN<<3],*Wn[30]; void init(){ int lg2=0,*pos=Pool,n,w; while((1<>=1; } } int build(int k){ int n,pos=0; while((1<>1]>>1)|((i&1)<<(pos-1)); return n; } void NTT(int *f,int n,bool type){ _for(i,0,n)if(i>1); int n=build((_n-1)<<1); _for(i,(_n+1)>>1,n)g[i]=0; _for(i,0,_n)temp[i]=f[i];_for(i,_n,n)temp[i]=0; NTT(g,n,true);NTT(temp,n,true); _for(i,0,n)g[i]=(2-1LL*temp[i]*g[i]%Mod)*g[i]%Mod; NTT(g,n,false); _for(i,_n,n)g[i]=0; } void Div(const int *f,int _n,const int *g,int _m,int *q,int *r){ static int temp[MAXN<<2]; if(_n<_m){ _rep(i,0,n)r[i]=f[i]; return; } _rep(i,0,_m)temp[i]=g[_m-i]; Inv(temp,q,_n-_m+1); _rep(i,0,_n)temp[i]=f[_n-i]; Mul(q,_n-_m+1,temp,_n+1,_n-_m+1); for(int i=0,j=_n-_m;i>1); _for(i,(_n+1)>>1,_n)g[i]=0; Ln(g,temp,_n); temp[0]=(1+f[0]-temp[0])%Mod; _for(i,1,_n)temp[i]=(f[i]-temp[i])%Mod; Mul(g,(_n+1)>>1,temp,_n,_n); } void Pow(const int *f,int *g,int _n,int k1,int k2){ static int temp[MAXN<<2]; int pos=0,posv; while(!f[pos]&&pos<_n)pos++; if(1LL*pos*k2>=_n){ _for(i,0,_n)g[i]=0; return; } posv=quick_pow(f[pos],Mod-2); _for(i,pos,_n)g[i-pos]=1LL*f[i]*posv%Mod; _for(i,_n-pos,_n)g[i]=0; Ln(g,temp,_n); _for(i,0,_n)temp[i]=1LL*temp[i]*k1%Mod; Exp(temp,g,_n); pos=pos*k2,posv=quick_pow(posv,1LL*k2*(Mod-2)%(Mod-1)); for(int i=_n-1;i>=pos;i--)g[i]=1LL*g[i-pos]*posv%Mod; _for(i,0,pos)g[i]=0; } void Sqrt(const int *f,int *g,int _n){ static int temp1[MAXN<<2],temp2[MAXN<<2]; if(_n==1) return g[0]=quick_pow(G,bsgs(3,f[0])/2),void(); Sqrt(f,g,(_n+1)>>1); _for(i,(_n+1)>>1,_n) g[i]=0; _for(i,0,_n) temp1[i]=f[i]; Inv(g,temp2,_n); Mul(temp1,_n,temp2,_n); int div2=quick_pow(2,Mod-2); _for(i,0,_n) g[i]=1LL*(g[i]+temp1[i])*div2%Mod; } } int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ int x; scanf("%d",&x); g[x]=(Mod-4)%Mod; } g[0]=1; Poly::init(); Poly::Sqrt(g,h,m+1); h[0]=(h[0]+1)%Mod; Poly::Inv(h,f,m+1); for(int i=1;i<=m;i++) printf("%d\n",1ll*f[i]*2%Mod); return 0; }