生成函数相关
首先对于函数F(x),在x0处泰勒展开,F(x)=+∞∑n=0Fn(x0)n!(x−x0)n,这个x的取值是有一定范围的,当然我们也不关心
若在x0=0处展开,即麦克劳林级数
(1−x)−1=+∞∑n=0xn(1−x)−m=+∞∑n=0(n+m−1n)xn(1+x)m=+∞∑n=0(mn)xnln(1−x)=−+∞∑n=1xnnln(1+x)=−+∞∑n=1(−1)nxnnexpx=+∞∑n=0xnn!
OGF
对于数列f,它的普通生成函数即为F(x)=+∞∑n=0fnxn,根据上式,对于任意数列都有一个函数对应
对于F(x),我们可以为其赋予一个组合意义
考虑集合F每个物品都有大小,对于fnxn,fn即为大小为n的物品(好像叫组合对象)
对于F(x)G(x),是考虑将FG这样拼接的方案,物品间不存在顺序
如F={′A′},G={′B′},F(x)G(x)即{′A′,′B′,′AB′}(考虑空点)
更形式化的,定义H为G,F的笛卡尔积,H中为G,F元素的有序二元组
定义|(a,b)|=|a|+|b|,则H(x)=F(x)G(x)
A为一些组合对象的集合,定义SEQA为A中元素形成的n元笛卡尔积(类似于排列)
SEQA(x)=1+A(x)+A2(x)+A3(x).....=11−A(x)
其实就是Ap(x)考虑选p个元素对答案的贡献
OGF可以解决一类方案数背包问题
对于一个物品,容量为vi,数量为ni,定义Ai(x)=(1+xvi+x2vi...+xnivi)=x(ni+1)vi−1xvu−1
对于容量V的方案A(x)=n∏i=1Ai(x)=n∏i=1x(ni+1)vi−1xvi−1
付公主的背包
这个背包最多可以装 105 大小的东西
付公主有 n 种商品,她要准备出摊了
每种商品体积为 vi,都有无限件
给定 m,对于 s∈[1,m],请你回答用这些商品恰好装 s 体积的方案数
对于容量V的方案A(x)=n∏i=1Ai(x)=n∏i=1x(ni+1)vi−1xvu−1=n∏i=111−xvi
同时去对数
lnA(x)=n∑i=1ln11−xvi=−n∑i=1ln(1−xvi)
根据上面的麦克劳林级数,即得n∑i=1+∞∑j=0xvijj
由于只取前m+1项,则lnA(x)=m∑j=01jn∑i=1xvij(mod xm+1)
考虑统计每个容量物品的个数Numi
lnA(x)=m∑j=01jm∑i=1Numixij(mod xm+1)=m∑j=01j⌊mj⌋∑i=1Numixij(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=0fnn!xn
对于1n!,相对于OGF,可以理解为物品间有顺序,可以给每个物品打上标号
相当于OGF考虑的是组合,EGF考虑的是排列
所以OGF又称为无标号计数,EGF称为有标号计数
EGF的卷积F(x)G(x)=∑i=0∑j=0fii!xigjj!xj=∑n=0xn∑i=0fii!gn−i(n−i)!=∑n=0xnn!∑i=0fii!gn−i(n−i)!n!=
∑n=0xnn!∑i=0fign−i(ni)
由此,EGF的卷积类似于有标号的计数的合并,又称为二次卷积
这里的合并不是两段序列接在一起,是在保持F,G原有先后顺序的前提下构成一个新的序列
A为带标号的组合对象集合,SEQA同样为n元笛卡尔积组成的集合
SEQA(x)=1+A(x)+A2(x)+A3(x).....=11−A(x)
SETA为n元笛卡尔积(不考虑顺序)组成的集合
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)不能直接取模
Bell数及相关的的第二类斯特林数
Bell数即将n个不同元素划分的方案数记为Bn
考虑现在有一个大小为m1的只有一种标号方法的集合A1,和大小为m2的A2
A1(x)A2(x)即为A1中的元素划分在一起,A2的元素划分在一起,而A1内部是无顺序的
由此Bell数为若干个子集合并而来,而子集内部不带顺序
考虑一个子集的EGF,我们为了内部无顺序因此先钦定顺序
则A(x)=x+x22+x33!+x44!...=ex−1
B(x)=1+A(x)+A(x)22+A(x)33!+A(x)44!...=eex−1
第二类斯特林数S(n,m)表示有n个不同的元素划分为m个不同的集合的方案
B(n)=n∑i=1S(n,i)
若给定m,考虑Am(x)m!就相当于选取m个集合合并
则S(n,m)给定m对应的生成函数即为(ex−1)mm!(好像要用快速幂)
形式化的+∞∑n=0S(n,m)xnn!=(ex−1)mm!
考虑左式二项式展开
(ex−1)mm!=1m!m∑k=0(mk)ekx(−1)m−k
S(n,m)=[xn]F(x)n!,ekx=∑n=0(kx)nn!
则S(n,m)=1m!m∑k=0(mk)knn!(−1)m−kn!=m∑k=0(−1)m−k(m−k)!×knk!
注意到这是卷积的形式
[TJOI2015]概率论
为了提高智商,ZJY 开始学习概率论。有一天,她想到了这样一个问题:对于一棵随机生成的 n 个结点的有根二叉树(所有互相不同构的形态等概率出现),它的叶子节点数的期望是多少呢?
首先明确n个节点的有根二叉树的方案数就是Catanlann=(2nn)−(2nn−1)
定义fi为有i个节点时叶子的总数
答案即为fi(2nn)−(2nn−1)
定义F(x)为f的OGF,G(x)为Catanlan的生成函数
考虑Catanlan的递推式
g0=1
gn=n−1∑i=0gign−i−1
观察到上面是卷积的形式,但g0不适用
据此G2(x)x+g0=G(x)=G2(x)x+1
解得G(x)=1−√1−4x2x,注意要舍去负的
再考虑f的递推式,钦定左边贡献叶子在对称右儿子
f0=0,f1=1
fn=2n−1∑i=0fign−1−i
还是类似于卷积
F(x)=2G(x)F(x)x+f0+f1x=(1−√1−4x)F(x)+x
即得F(x)=x√1−4x
考虑∫F(x)xdx=∫−14(1−4x)−12(1−4x)′dx=1−√1−4x2+C=xG(x)
即(xG(x))′=∑i=0gi(xi+1)′=∑i=0gii(xi)
由此得fi=igi−1,gi=(2ii)−(2ii−1)
答案即为n(n+1)2n(2n+1)(好难)
The Child and Binary Tree
我们的小朋友很喜欢计算机科学,而且尤其喜欢二叉树。 考虑一个含有 n 个互异正整数的序列 c1,c2⋯,cn。如果一棵带点权的有根二叉树满足其所有顶点的权值都在集合 {c1,c2,⋯,cn} 中,我们的小朋友就会将其称作神犇的。
并且他认为,一棵带点权的树的权值,是其所有顶点权值的总和。
给出一个整数 m,你能对于任意的 1≤s≤m 计算出权值为 s 的神犇二叉树的个数吗?请参照样例以更好的理解什么样的两棵二叉树会被视为不同的。 我们只需要知道答案关于 998244353 取模后的值。
输入第一行有 2 个整数 n,m (1≤n,m≤105)。 第二行有 n 个用空格隔开的互异的整数 c1,c2⋯,cn (1≤ci≤105)。
输出 m 行,每行有一个整数。第 i 行应当含有权值恰为 i 的神犇二叉树的总数。请输出答案关于 998244353 取模的结果。
定义fi为权值为i的神犇二叉树的总数,F(x)为其生成函数,f0=1
fi=n∑j=1∑k=0fkfi−cj−k
设gi表示i这个权值是否存在
fn=m∑i=1gin−i∑j=0fjfn−i−j,n≥1
注意到F2(x)G(x)+f0=F(x)=F2(x)G(x)+1
解得F(x)=1−√1−4G(x)2G(x)
注意分母的常数项为0,要调整成21+√1−4G(x)
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通