浅谈生成函数

生成函数相关

首先对于函数F(x),在x0处泰勒展开,F(x)=n=0+Fn(x0)n!(xx0)n,这个x的取值是有一定范围的,当然我们也不关心

若在x0=0处展开,即麦克劳林级数

(1x)1=n=0+xn(1x)m=n=0+(n+m1n)xn(1+x)m=n=0+(mn)xnln(1x)=n=1+xnnln(1+x)=n=1+(1)nxnnexpx=n=0+xnn!

OGF

对于数列f,它的普通生成函数即为F(x)=n=0+fnxn,根据上式,对于任意数列都有一个函数对应

对于F(x),我们可以为其赋予一个组合意义

考虑集合F每个物品都有大小,对于fnxn,fn即为大小为n的物品(好像叫组合对象)

对于F(x)G(x),是考虑将FG这样拼接的方案,物品间不存在顺序

F={A},G={B},F(x)G(x){A,B,AB}(考虑空点)

更形式化的,定义HG,F的笛卡尔积,H中为G,F元素的有序二元组

定义|(a,b)|=|a|+|b|,则H(x)=F(x)G(x)


A为一些组合对象的集合,定义SEQAA中元素形成的n元笛卡尔积(类似于排列)

SEQA(x)=1+A(x)+A2(x)+A3(x).....=11A(x)

其实就是Ap(x)考虑选p个元素对答案的贡献


OGF可以解决一类方案数背包问题

对于一个物品,容量为vi,数量为ni,定义Ai(x)=(1+xvi+x2vi...+xnivi)=x(ni+1)vi1xvu1

对于容量V的方案A(x)=i=1nAi(x)=i=1nx(ni+1)vi1xvi1


付公主的背包

这个背包最多可以装 105 大小的东西

付公主有 n 种商品,她要准备出摊了

每种商品体积为 vi,都有无限件

给定 m,对于 s[1,m],请你回答用这些商品恰好装 s 体积的方案数

对于容量V的方案A(x)=i=1nAi(x)=i=1nx(ni+1)vi1xvu1=i=1n11xvi

同时去对数

lnA(x)=i=1nln11xvi=i=1nln(1xvi)

根据上面的麦克劳林级数,即得i=1nj=0+xvijj

由于只取前m+1项,则lnA(x)=j=0m1ji=1nxvij(mod xm+1)

考虑统计每个容量物品的个数Numi

lnA(x)=j=0m1ji=1mNumixij(mod xm+1)=j=0m1ji=1mjNumixij(mod xm+1)

直接枚举即可,调和级数得时间复杂度为Θ(nlogn),最后Exp即可

分拆数就是物品权值为i的背包,直接套用即可

#include<bits/stdc++.h> #define eps 1e-9 using namespace std; const int MAXN=1e5+5; const int MOD=998244353; const int g=3; const long double Pi=acos(-1.0); const int p=32000; int Rev[MAXN*4]; struct Cpx{ long double a,b; Cpx(){ a=0; b=0; } Cpx(long double aa,long double bb){ a=aa; b=bb; } Cpx operator*(const Cpx x)const{ return Cpx(a*x.a-b*x.b,b*x.a+a*x.b); } Cpx operator+(const Cpx x)const{ return Cpx(a+x.a,b+x.b); } Cpx operator-(const Cpx x)const{ return Cpx(a-x.a,b-x.b); } }; int Pow(int a,int b,int pff){ int res=1; int base=a; while(b) { if(b&1) { res=((long long)res*base)%pff; } base=((long long)base*base)%pff; b>>=1; } return res; } int inv(int a,int pff){ return Pow(a,pff-2,pff); } struct Poly{ vector<int>U; vector<Cpx>V; int size() { return U.size(); } void push_back(int x) { U.push_back(x); return; } void clear() { U.clear(); return; } void NTT(int Limit,int type) { int Len=(1<<Limit); for(int i=0;i<Len;i++) { Rev[i]=((Rev[i>>1]>>1)|((i&1)<<(Limit-1))); } while(U.size()<Len) { U.push_back(0); } for(int i=0;i<Len;i++) { if(i<Rev[i]) { swap(U[i],U[Rev[i]]); } } for(int l=1;l<Len;l<<=1) { int Wn=Pow(g,(MOD-1)/(l<<1),MOD); if(type==-1) { Wn=inv(Wn,MOD); } for(int i=0;i<Len;i+=(l<<1)) { int W=1; for(int j=i;j<i+l;j++,W=((long long)W*Wn)%MOD) { int Xc=U[j]; int Yc=((long long)U[j+l]*W)%MOD; U[j]=((long long)Xc+Yc)%MOD; U[j+l]=((long long)Xc-Yc+MOD)%MOD; } } } if(type==-1) { int Liv=inv(Len,MOD); for(int i=0;i<Len;i++) { U[i]=((long long)U[i]*Liv)%MOD; } } } }; Poly Mul_NTT(Poly A,Poly B){ int N=A.U.size(); int M=B.U.size(); int nox=1; int Lm=0; while(nox<=(N+M-2)) { nox<<=1; Lm++; } A.NTT(Lm,1); B.NTT(Lm,1); for(int i=0;i<nox;i++) { A.U[i]=((long long)A.U[i]*B.U[i])%MOD; } A.NTT(Lm,-1); while(A.U.size()>(N+M-1)) { A.U.pop_back(); } return A; } Poly Inverse(Poly A,int N){ Poly Fn; Fn.U.clear(); Fn.U.push_back(inv(A.U[0],MOD)); if(N==1) { return Fn; } for(int l=2,Lm=1;(l>>1)<N;l<<=1,Lm++) { Poly H; H.U.clear(); for(int j=0;j<l;j++) { if(j<A.U.size()) { H.U.push_back(A.U[j]); } else { H.U.push_back(0); } } H.NTT(Lm+1,1); Fn.NTT(Lm+1,1); for(int j=0;j<l*2;j++) { Fn.U[j]=((long long)Fn.U[j]*(2-((long long)Fn.U[j]*H.U[j])%MOD+MOD)%MOD)%MOD; } Fn.NTT(Lm+1,-1); while(Fn.U.size()>l) { Fn.U.pop_back(); } } while(Fn.U.size()>N) { Fn.U.pop_back(); } return Fn; } Poly Der(Poly x){ Poly Nex; Nex.U.clear(); for(int i=1;i<x.U.size();i++){ Nex.U.push_back(((long long)i*x.U[i])%MOD); } return Nex; } Poly Ing(Poly x){ Poly Nex; Nex.U.clear(); Nex.U.push_back(0); for(int i=0;i<x.U.size();i++) { Nex.U.push_back(((long long)x.U[i]*inv(i+1,MOD))%MOD); } return Nex; } Poly Ln(Poly x,int N){ Poly ex=Der(x); Poly ey=Inverse(x,N); ex=Mul_NTT(ex,ey); ex=Ing(ex); while(ex.U.size()>N) { ex.U.pop_back(); } return ex; } Poly Exp(Poly A,int N){ Poly Fn; Fn.U.clear(); Fn.U.push_back(1); if(N==1) { return Fn; } for(int l=2,Lm=1;(l>>1)<N;l<<=1,Lm++) { Poly H; H.U.clear(); for(int j=0;j<l;j++) { if(j<A.U.size()) { H.U.push_back(A.U[j]); } else { H.U.push_back(0); } } Poly Fln=Ln(Fn,l); H.NTT(Lm+1,1); Fn.NTT(Lm+1,1); Fln.NTT(Lm+1,1); for(int j=0;j<l*2;j++) { Fn.U[j]=((long long)Fn.U[j]*(((long long)H.U[j]+1-Fln.U[j]+MOD)%MOD))%MOD; } Fn.NTT(Lm+1,-1); while(Fn.U.size()>l) { Fn.U.pop_back(); } } while(Fn.U.size()>N) { Fn.U.pop_back(); } return Fn; } int n; int m; int Num[MAXN]; int v; signed main(){ scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&v); Num[v]++; } Poly A; A.clear(); A.U.resize(m+1); for(int j=1;j<=m;j++) { int FUc=inv(j,MOD); for(int i=1;i<=(m/j);i++) { A.U[i*j]=((long long)A.U[i*j]+((long long)Num[i]*FUc)%MOD)%MOD; } } A=Exp(A,m+1); for(int i=1;i<=m;i++) { printf("%d\n",A.U[i]); } }

EGF

对于数列f,它的指数生成函数即为F(x)=n=0+fnn!xn

对于1n!,相对于OGF,可以理解为物品间有顺序,可以给每个物品打上标号

相当于OGF考虑的是组合,EGF考虑的是排列

所以OGF又称为无标号计数,EGF称为有标号计数

EGF的卷积F(x)G(x)=i=0j=0fii!xigjj!xj=n=0xni=0fii!gni(ni)!=n=0xnn!i=0fii!gni(ni)!n!=

n=0xnn!i=0figni(ni)

由此,EGF的卷积类似于有标号的计数的合并,又称为二次卷积

这里的合并不是两段序列接在一起,是在保持F,G原有先后顺序的前提下构成一个新的序列


A为带标号的组合对象集合,SEQA同样为n元笛卡尔积组成的集合

SEQA(x)=1+A(x)+A2(x)+A3(x).....=11A(x)

SETAn元笛卡尔积(不考虑顺序)组成的集合

SETA(x)=1+A(x)+A2(x)2!+A3(x)3!+A4(x)4!....=eA(x)

注意,对于SEQ,笛卡尔积本身是有顺序的,EGF这样相当于合并时不同集合有先后,一般没有运用场景,而OGF的笛卡尔积是在A,B集合各选一个拼接,A,B内部是无顺序的

对于SET,EGF就是合并A,B的元素然后重标号,OGF则相当于把集合A的大小扩展了,同样用不上


[集训队作业2013]城市规划

刚刚解决完电力网络的问题,阿狸又被领导的任务给难住了。

刚才说过,阿狸的国家有 n 个城市,现在国家需要在某些城市对之间建立一些贸易路线,使得整个国家的任意两个城市都直接或间接的连通。

为了省钱, 每两个城市之间最多只能有一条直接的贸易路径。对于两个建立路线的方案,如果存在一个城市对,在两个方案中是否建立路线不一样,那么这两个方案就是不同的,否则就是相同的。现在你需要求出一共有多少不同的方案。

好了,这就是困扰阿狸的问题。换句话说,你需要求出 n 个点的简单 (无重边无自环) 有标号无向连通图数目。

由于这个数字可能非常大, 你只需要输出方案数对 1004535809 ( 479×221+1 ) 即可。

考虑任意一个简单无向图与连通图的关系

带标号无向图实际上就是一些带标号连通图合并而成

F(x)为有n个点无向图的方案的指数生成函数,G(x)为有n个点连通图的方案的指数生成函数

F(x)=SETG=exp(G(x))

G(x)=ln(F(x))

考虑F(x)的构成

F(x)=n=02(n2)xnn!

(n2)是边的总数,考虑先给点标号,然后边选或不选

注意模数不一样,g=3,(n2)不能直接取模

#include<bits/stdc++.h> #define eps 1e-9 using namespace std; const int MAXN=2e5+5; const int MOD=1004535809; const int g=3; int Rev[MAXN*4]; int Pow(int a,int b,int pff){ int res=1; int base=a; while(b) { if(b&1) { res=((long long)res*base)%pff; } base=((long long)base*base)%pff; b>>=1; } return res; } int inv(int a,int pff){ return Pow(a,pff-2,pff); } struct Poly{ vector<int>U; int size() { return U.size(); } void push_back(int x) { U.push_back(x); return; } void clear() { U.clear(); return; } void NTT(int Limit,int type) { int Len=(1<<Limit); for(int i=0;i<Len;i++) { Rev[i]=((Rev[i>>1]>>1)|((i&1)<<(Limit-1))); } while(U.size()<Len) { U.push_back(0); } for(int i=0;i<Len;i++) { if(i<Rev[i]) { swap(U[i],U[Rev[i]]); } } for(int l=1;l<Len;l<<=1) { int Wn=Pow(g,(MOD-1)/(l<<1),MOD); if(type==-1) { Wn=inv(Wn,MOD); } for(int i=0;i<Len;i+=(l<<1)) { int W=1; for(int j=i;j<i+l;j++,W=((long long)W*Wn)%MOD) { int Xc=U[j]; int Yc=((long long)U[j+l]*W)%MOD; U[j]=((long long)Xc+Yc)%MOD; U[j+l]=((long long)Xc-Yc+MOD)%MOD; } } } if(type==-1) { int Liv=inv(Len,MOD); for(int i=0;i<Len;i++) { U[i]=((long long)U[i]*Liv)%MOD; } } } }; Poly Mul_NTT(Poly A,Poly B){ int N=A.U.size(); int M=B.U.size(); int nox=1; int Lm=0; while(nox<=(N+M-2)) { nox<<=1; Lm++; } A.NTT(Lm,1); B.NTT(Lm,1); for(int i=0;i<nox;i++) { A.U[i]=((long long)A.U[i]*B.U[i])%MOD; } A.NTT(Lm,-1); while(A.U.size()>(N+M-1)) { A.U.pop_back(); } return A; } Poly Inverse(Poly A,int N){ Poly Fn; Fn.U.clear(); Fn.U.push_back(inv(A.U[0],MOD)); if(N==1) { return Fn; } for(int l=2,Lm=1;(l>>1)<N;l<<=1,Lm++) { Poly H; H.U.clear(); for(int j=0;j<l;j++) { if(j<A.U.size()) { H.U.push_back(A.U[j]); } else { H.U.push_back(0); } } H.NTT(Lm+1,1); Fn.NTT(Lm+1,1); for(int j=0;j<l*2;j++) { Fn.U[j]=((long long)Fn.U[j]*(2-((long long)Fn.U[j]*H.U[j])%MOD+MOD)%MOD)%MOD; } Fn.NTT(Lm+1,-1); while(Fn.U.size()>l) { Fn.U.pop_back(); } } while(Fn.U.size()>N) { Fn.U.pop_back(); } return Fn; } Poly Der(Poly x){ Poly Nex; Nex.U.clear(); for(int i=1;i<x.U.size();i++){ Nex.U.push_back(((long long)i*x.U[i])%MOD); } return Nex; } Poly Ing(Poly x){ Poly Nex; Nex.U.clear(); Nex.U.push_back(0); for(int i=0;i<x.U.size();i++) { Nex.U.push_back(((long long)x.U[i]*inv(i+1,MOD))%MOD); } return Nex; } Poly Ln(Poly x,int N){ Poly ex=Der(x); Poly ey=Inverse(x,N); ex=Mul_NTT(ex,ey); ex=Ing(ex); while(ex.U.size()>N) { ex.U.pop_back(); } return ex; } int n; signed main(){ scanf("%d",&n); int Mul=1; Poly F; F.clear(); F.U.resize(n+1); for(int i=0;i<=n;i++) { if(i==0) { Mul=1; } else { Mul=((long long)Mul*i)%MOD; } long long Kx=((long long)i*(i-1)); Kx=((long long)Kx/2); Kx=Pow(2,(Kx%(MOD-1)),MOD); Kx=((long long)Kx*inv(Mul,MOD))%MOD; F.U[i]=Kx; } F=Ln(F,n+1); Mul=1; for(int i=0;i<F.size();i++) { if(i==0) { Mul=1; } else { Mul=((long long)Mul*i)%MOD; } F.U[i]=((long long)F.U[i]*Mul)%MOD; } printf("%d\n",F.U[n]); }

Bell数及相关的的第二类斯特林数

Bell数即将n个不同元素划分的方案数记为Bn

考虑现在有一个大小为m1的只有一种标号方法的集合A1,和大小为m2A2

A1(x)A2(x)即为A1中的元素划分在一起,A2的元素划分在一起,而A1内部是无顺序的

由此Bell数为若干个子集合并而来,而子集内部不带顺序

考虑一个子集的EGF,我们为了内部无顺序因此先钦定顺序

A(x)=x+x22+x33!+x44...=ex1

B(x)=1+A(x)+A(x)22+A(x)33!+A(x)44...=eex1

第二类斯特林数S(n,m)表示有n个不同的元素划分为m个不同的集合的方案

B(n)=i=1nS(n,i)

若给定m,考虑Am(x)m!就相当于选取m个集合合并

S(n,m)给定m对应的生成函数即为(ex1)mm!(好像要用快速幂)

形式化的n=0+S(n,m)xnn!=(ex1)mm!

考虑左式二项式展开

(ex1)mm!=1m!k=0m(mk)ekx(1)mk

S(n,m)=[xn]F(x)n!,ekx=n=0(kx)nn!

S(n,m)=1m!k=0m(mk)knn!(1)mkn!=k=0m(1)mk(mk)!×knk!

注意到这是卷积的形式


[TJOI2015]概率论

为了提高智商,ZJY 开始学习概率论。有一天,她想到了这样一个问题:对于一棵随机生成的 n 个结点的有根二叉树(所有互相不同构的形态等概率出现),它的叶子节点数的期望是多少呢?

首先明确n个节点的有根二叉树的方案数就是Catanlann=(2nn)(2nn1)

定义fi为有i个节点时叶子的总数

答案即为fi(2nn)(2nn1)

定义F(x)fOGF,G(x)Catanlan的生成函数

考虑Catanlan的递推式

g0=1

gn=i=0n1gigni1

观察到上面是卷积的形式,但g0不适用

据此G2(x)x+g0=G(x)=G2(x)x+1

解得G(x)=114x2x,注意要舍去负的

再考虑f的递推式,钦定左边贡献叶子在对称右儿子

f0=0,f1=1

fn=2i=0n1fign1i

还是类似于卷积

F(x)=2G(x)F(x)x+f0+f1x=(114x)F(x)+x

即得F(x)=x14x

考虑F(x)xdx=14(14x)12(14x)dx=114x2+C=xG(x)

(xG(x))=i=0gi(xi+1)=i=0gii(xi)

由此得fi=igi1,gi=(2ii)(2ii1)

答案即为n(n+1)2n(2n+1)(好难)


The Child and Binary Tree

我们的小朋友很喜欢计算机科学,而且尤其喜欢二叉树。 考虑一个含有 n 个互异正整数的序列 c1,c2,cn。如果一棵带点权的有根二叉树满足其所有顶点的权值都在集合 {c1,c2,,cn} 中,我们的小朋友就会将其称作神犇的。

并且他认为,一棵带点权的树的权值,是其所有顶点权值的总和。

给出一个整数 m,你能对于任意的 1sm 计算出权值为 s 的神犇二叉树的个数吗?请参照样例以更好的理解什么样的两棵二叉树会被视为不同的。 我们只需要知道答案关于 998244353 取模后的值。

输入第一行有 2 个整数 n,m (1n,m105)。 第二行有 n 个用空格隔开的互异的整数 c1,c2,cn (1ci105)

输出 m 行,每行有一个整数。第 i 行应当含有权值恰为 i 的神犇二叉树的总数。请输出答案关于 998244353 取模的结果。

定义fi为权值为i的神犇二叉树的总数,F(x)为其生成函数,f0=1

fi=j=1nk=0fkficjk

gi表示i这个权值是否存在

fn=i=1mgij=0nifjfnij,n1

注意到F2(x)G(x)+f0=F(x)=F2(x)G(x)+1

解得F(x)=114G(x)2G(x)

注意分母的常数项为0,要调整成21+14G(x)


__EOF__

本文作者Yukino
本文链接https://www.cnblogs.com/kid-magic/p/17070965.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   kid_magic  阅读(221)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示