指数型生成函数(EGF)学习笔记
之前,我们学习过如何使用生成函数来做一些组合问题(比如背包问题),但是它面对排列问题(有标号)的时候就束手无策了。
究其原因,是因为排列问题的递推式有一些系数(这个待会就知道了),所以我们可以修改一下生成函数的式子。
对于数列$\{a_n\}$,它的指数型生成函数(EGF)为
$$F^{(e)}(x)=\sum_{i=0}^{+\infty}a_i*\frac{x^i}{i!}$$
至于为什么叫指数形式呢?是因为当$a_n=1$时,$F^{(e)}(x)=\sum_{i=0}^{+\infty}\frac{x^i}{i!}=e^x$
而且对于其他更复杂的EGF也都可以用$e^x$表示出来。
然后我们看看EGF如何做计数问题。
例题1:对于一个长为$n-2$的序列,元素为$[1,n]$中的整数,且出现次数最多的元素出现$m-1$次,求不同的序列个数。
数据范围:$n,m\leq 5*10^4$
这道题可以先转化为出现次数$\leq m-1$减去出现次数$\leq m-2$。
我们假设$i$在这个序列中出现了$a_i$次。
则答案为$\frac{(n-2)!}{\prod_{i=1}^na_i!}$,其中$a_i<m,\sum_{i=1}^na_i=n-2$
所以我们构造
$$F(x)=\sum_{i=0}^{m-1}\frac{x^i}{i!}$$
$$Ans=(n-2)![x^{n-2}]F^n(x)$$
例题2:对于$n$个节点的有标号无根树,每个节点的度数的最大值为$m$,求这样的树的个数。
首先你要知道一个东西叫$prufer$序列,如果想学的可以自行百度,如果不想学的只需知道一下几点。
1.$n$个节点的有标号无根树与长为$n-2$的,元素为$[1,n]$之间整数的序列有一一对应的关系。
2.这个序列中,$i$这个数出现次数$a_i=d_i-1$其中$d_i$为$i$的度数
然后你就知道它和例题2是一样的了。
#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = , mod = , G = , Gi = ;
int n, m, fac[N], inv[N], F[N];
inline int add(int a, int b){int x = a + b; if(x >= mod) x -= mod; return x;}
inline int dec(int a, int b){int x = a - b; if(x < ) x += mod; return x;}
inline int mul(int a, int b){return (LL) a * b - (LL) a * b / mod * mod;}
inline int kasumi(int a, int b){
int res = ;
while(b){
if(b & ) res = mul(res, a);
a = mul(a, a);
b >>= ;
}
return res;
}
int rev[N];
inline void NTT(int *A, int limit, int type){
for(Rint i = ;i < limit;i ++)
if(i < rev[i]) swap(A[i], A[rev[i]]);
for(Rint mid = ;mid < limit;mid <<= ){
int Wn = kasumi(type == ? G : Gi, (mod - ) / (mid << ));
for(Rint j = ;j < limit;j += mid << ){
int w = ;
for(Rint k = ;k < mid;k ++, w = mul(w, Wn)){
int x = A[j + k], y = mul(A[j + k + mid], w);
A[j + k] = add(x, y);
A[j + k + mid] = dec(x, y);
}
}
}
if(type == -){
int inv = kasumi(limit, mod - );
for(Rint i = ;i < limit;i ++)
A[i] = mul(A[i], inv);
}
}
int ans[N];
inline void poly_inv(int *A, int deg){
static int tmp[N];
if(deg == ){
ans[] = kasumi(A[], mod - );
return;
}
poly_inv(A, deg + >> );
int limit = , L = -;
while(limit <= (deg << )){limit <<= ; L ++;}
for(Rint i = ;i < limit;i ++)
rev[i] = (rev[i >> ] >> ) | ((i & ) << L);
for(Rint i = ;i < deg;i ++) tmp[i] = A[i];
for(Rint i = deg;i < limit;i ++) tmp[i] = ;
NTT(tmp, limit, ); NTT(ans, limit, );
for(Rint i = ;i < limit;i ++) ans[i] = mul(dec(, mul(ans[i], tmp[i])), ans[i]);
NTT(ans, limit, -);
for(Rint i = deg;i < limit;i ++) ans[i] = ;
}
int Ln[N];
inline void poly_Ln(int *A, int deg){
static int tmp[N];
poly_inv(A, deg);
for(Rint i = ;i < deg;i ++) tmp[i - ] = mul(i, A[i]);
tmp[deg - ] = ;
int limit = , L = -;
while(limit <= (deg << )){limit <<= ; L ++;}
for(Rint i = ;i < limit;i ++)
rev[i] = (rev[i >> ] >> ) | ((i & ) << L);
NTT(tmp, limit, ); NTT(ans, limit, );
for(Rint i = ;i < limit;i ++) Ln[i] = mul(tmp[i], ans[i]);
NTT(Ln, limit, -);
for(Rint i = deg + ;i < limit;i ++) Ln[i] = ;
for(Rint i = deg;i;i --) Ln[i] = mul(Ln[i - ], kasumi(i, mod - ));
Ln[] = ;
for(Rint i = ;i < limit;i ++) tmp[i] = ans[i] = ;
}
int Exp[N];
inline void poly_Exp(int *A, int deg){
if(deg == ){
Exp[] = ;
return;
}
poly_Exp(A, deg + >> );
poly_Ln(Exp, deg);
int limit = , L = -;
while(limit <= (deg << )){limit <<= ; L ++;}
for(Rint i = ;i < limit;i ++)
rev[i] = (rev[i >> ] >> ) | ((i & ) << L);
for(Rint i = ;i < deg;i ++) Ln[i] = dec(A[i] + (i == ), Ln[i]);
NTT(Ln, limit, ); NTT(Exp, limit, );
for(Rint i = ;i < limit;i ++) Exp[i] = mul(Exp[i], Ln[i]);
NTT(Exp, limit, -);
for(Rint i = ;i < limit;i ++) Ln[i] = ans[i] = ;
for(Rint i = deg;i < limit;i ++) Exp[i] = ;
}
inline void init(int m){
fac[] = fac[] = ;
for(Rint i = ;i <= m;i ++) fac[i] = mul(i, fac[i - ]);
inv[] = ; inv[] = ;
for(Rint i = ;i <= m;i ++) inv[i] = mul(inv[mod % i], mod - mod / i);
inv[] = ;
for(Rint i = ;i <= m;i ++) inv[i] = mul(inv[i], inv[i - ]);
}
inline int solve(int m){
memset(Exp, , sizeof Exp);
for(Rint i = ;i < m;i ++) F[i] = inv[i];
for(Rint i = m;i < n;i ++) F[i] = ;
poly_Ln(F, n);
for(Rint i = ;i < n;i ++) F[i] = mul(Ln[i], n), Ln[i] = ;
poly_Exp(F, n);
return mul(fac[n - ], Exp[n - ]);
}
int main(){
scanf("%d%d", &n, &m);
init(n);
printf("%d", dec(solve(m), solve(m - )));
}
我们从上面这道题可以看出,其实就是去标号的思想,转化为组合问题,然后就可以用生成函数了。
例题3:求$n$个点的有标号无向连通图的个数
我们假设$n$个点的有标号无向图个数$/n!$为$g_n$,答案$/n!$为$f_n$
设这个无向图中有$k$个联通块,因为这$k$个联通块无标号,所以
$$G=\sum_{k=0}^{+\infty}\frac{F^k}{k!}=e^F$$
所以
$$F=\ln G$$
没了?没了。
#include<cstdio>
#include<algorithm>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = , mod = , g = , gi = ;
inline int kasumi(int a, int b){
int res = ;
while(b){
if(b & ) res = (LL) res * a % mod;
a = (LL) a * a % mod;
b >>= ;
}
return res;
}
int rev[N];
inline void NTT(int *A, int limit, int type){
for(Rint i = ;i < limit;i ++)
if(i < rev[i]) swap(A[i], A[rev[i]]);
for(Rint mid = ;mid < limit;mid <<= ){
int Wn = kasumi(type == ? g : gi, (mod - ) / (mid << ));
for(Rint j = ;j < limit;j += mid << ){
int w = ;
for(Rint k = ;k < mid;k ++, w = (LL) w * Wn % mod){
int x = A[j + k], y = (LL) w * A[j + k + mid] % mod;
A[j + k] = (x + y) % mod;
A[j + k + mid] = (x - y + mod) % mod;
}
}
}
if(type == -){
int inv = kasumi(limit, mod - );
for(Rint i = ;i < limit;i ++)
A[i] = (LL) A[i] * inv % mod;
}
}
int ans[N];
inline void poly_inv(int *A, int deg){
static int tmp[N];
if(deg == ){
ans[] = kasumi(A[], mod - );
return;
}
poly_inv(A, deg + >> );
int limit = , L = -;
while(limit <= (deg << )){limit <<= ; L ++;}
for(Rint i = ;i < limit;i ++) rev[i] = (rev[i >> ] >> ) | ((i & ) << L);
for(Rint i = ;i < deg;i ++) tmp[i] = A[i];
for(Rint i = deg;i < limit;i ++) tmp[i] = ;
NTT(tmp, limit, ); NTT(ans, limit, );
for(Rint i = ;i < limit;i ++) ans[i] = ( - (LL) ans[i] * tmp[i] % mod + mod) % mod * ans[i] % mod;
NTT(ans, limit, -);
for(Rint i = ;i < limit;i ++) tmp[i] = ;
for(Rint i = deg;i < limit;i ++) ans[i] = ;
}
int Ln[N];
inline void poly_Ln(int *A, int deg){
static int tmp[N];
poly_inv(A, deg);
for(Rint i = ;i < deg;i ++) tmp[i - ] = (LL) i * A[i] % mod;
tmp[deg - ] = ;
int limit = , L = -;
while(limit <= (deg << )){limit <<= ; L ++;}
for(Rint i = ;i < limit;i ++)
rev[i] = (rev[i >> ] >> ) | ((i & ) << L);
NTT(ans, limit, ); NTT(tmp, limit, );
for(Rint i = ;i < limit;i ++) Ln[i] = (LL) ans[i] * tmp[i] % mod;
NTT(Ln, limit, -);
for(Rint i = deg + ;i < limit;i ++) Ln[i] = ;
for(Rint i = deg;i;i --) Ln[i] = (LL) Ln[i - ] * kasumi(i, mod - ) % mod;
Ln[] = ;
}
int n, A[N], fac[N];
int main(){
scanf("%d", &n);
fac[] = ;
for(Rint i = ;i <= n;i ++) fac[i] = (LL) i * fac[i - ] % mod;
for(Rint i = ;i <= n;i ++)
A[i] = (LL) kasumi(, ((LL) i * (i - ) / ) % (mod - )) * kasumi(fac[i], mod - ) % mod;
poly_Ln(A, n + );
printf("%d", (LL) Ln[n] * fac[n] % mod);
}
指数型生成函数(EGF)学习笔记的更多相关文章
- FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅲ
第三波,走起~~ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅱ 单位根反演 今天打多校时 1002 被卡科技了 ...
- FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅱ
因为垃圾电脑太卡了就重开了一个... 前传:多项式Ⅰ u1s1 我预感还会有Ⅲ 多项式基础操作: 例题: 26. CF438E The Child and Binary Tree 感觉这题作为第一题还 ...
- 操作系统学习笔记(五)--CPU调度
由于第四章线程的介绍没有上传视频,故之后看书来补. 最近开始学习操作系统原理这门课程,特将学习笔记整理成技术博客的形式发表,希望能给大家的操作系统学习带来帮助.同时盼望大家能对文章评论,大家一起多多交 ...
- DBus学习笔记
摘要:DBus作为一个轻量级的IPC被越来越多的平台接受,在MeeGo中DBus也是主要的进程间通信方式,这个笔记将从基本概念开始记录笔者学习DBus的过程 [1] DBus学习笔记一:DBus学习的 ...
- OpenCV之Python学习笔记
OpenCV之Python学习笔记 直都在用Python+OpenCV做一些算法的原型.本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段.现在看 到一本国外的新书< ...
- javascript - 浏览TOM大叔博客的学习笔记
part1 ---------------------------------------------------------------------------------------------- ...
- ajax跨域请求学习笔记
原文:ajax跨域请求学习笔记 前言 ajax,用苍白的话赞扬:很好. 我们可以使用ajax实现异步获取数据,减少服务器运算时间,大大地改善用户体验:我们可以使用ajax实现小系统组合大系统:我们还可 ...
- Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
- Beego学习笔记
Beego学习笔记 Go 路由(Controller) 路由就是根据用户的请求找到需要执行的函数或者controller. Get /v1/shop/nike ShopController Get D ...
随机推荐
- 适用于 Windows 10 的触摸板手势
高级用户! 在 Windows 10 笔记本电脑的触摸板上试用这些手势: 选择项目:点击触摸板. 滚动:将两根手指放在触摸板上,然后以水平或垂直方向滑动. 放大或缩小:将两根手指放在触摸板上,然后收缩 ...
- 设置GRUB密码以防止单用户模式下root密码被恶意更改
在使用LInux系统的时候可能会发生忘记root密码的情况,通常管理员会进入单用户模式下进行重置root密码.那么问题来了,既然管理员可以进入单用户模式,如果恶意用户可以接触的到计算机的话毫无疑问也是 ...
- mybatis查询结果和接收的不一样
记一次大坑:mybatis查询结果和接收的不一样,折腾我好几个小时. 先上代码:代码是要查询排名,sql执行的结果 SELECT b.operator_id, b.class_count, b.cla ...
- myeclipse 从数据库生成java实体类
- mac os下不同工具go env下gopath显示不同
设置 vim ~/.zshrc 设置 vim ~/.bash_profile
- 第四百一十二节,python接口,抽象方法抽象类
Python接口 在Python中所谓的接口,有两种,一种是通过url访问的api接口 一种是一个对象的接口 构造接口 class Ijiekou: """ 定义一个约束 ...
- Cesium高度解析
var viewer = new Cesium.Viewer('cesiumContainer', { shadows : true }); //为true时,球体会有高程遮挡效果(在没有地形时候也会 ...
- python2与python3中除法的区别
python2中的除法 >>>1/2 0 即一个整数(无小数部分的数)被另外一个整数除,计算结果的小数部分被截除了,只留下了整数部分 有时候,这个功能比较有用,譬如在做一些需要取位数 ...
- Pointer-network的tensorflow实现-1
pointer-network是最近seq2seq比较火的一个分支,在基于深度学习的阅读理解,摘要系统中都被广泛应用. 感兴趣的可以阅读原paper 推荐阅读 https://medium.com/@ ...
- Qt编写自定义控件8-动画按钮组控件
前言 动画按钮组控件可以用来当做各种漂亮的导航条用,既可以设置成顶部底部+左侧右侧,还自带精美的滑动效果,还可以设置悬停滑动等各种颜色,原创作者雨田哥(QQ:3246214072),驰骋Qt控件界多年 ...