指数型生成函数(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 ...
随机推荐
- Super expression must either be null or a function, not undefined
按照之前买的用JavaScript开发移动应用的例子来编写的,然后报了这个错.我的头部声明是这样的 var React = require('react-native'); var { Text, V ...
- 【iCore4 双核心板_ARM】例程三十七:SDRAM实验——读写SDRAM
实验现象: 上电即开始读写SDRAM测试,测试过程中,蓝色LED点亮,如果出现错误,红色LED闪烁,测试成功,绿色LED点亮. 核心代码: int main(void) { /* USER CODE ...
- Ceph相关
Ceph基础知识和基础架构简介 http://www.xuxiaopang.com/2020/10/09/list/#more大话Ceph http://www.xuxiaopang.com/2016 ...
- tomcat 配置 使用综合
[参考]Tomcat 7.0安装与配置 [参考]tomcat 控制台日志(startup.bat)输出到指定文件中 [参考]将Java web应用部署到Tomcat 及部署到Tomcat根目录 的三种 ...
- Unity编辑器中分割线拖拽的实现
GUI splitter control How can I make a GUI splitter control, similar to the splitter the console has? ...
- 自动化测试工具Ranorex的录制功能使用
由于帆软的 Report 包含gui和web端 设计器 web预览 做自动化测试不适合使用 Katalon 发现了Ranorex Ranorex 是一款在Windows操作系统的上运行的GUI自动测试 ...
- mysql问题解决SELECT list is not in GROUP BY clause and contains nonaggregated column
今天在Ubuntu下的部署项目,发现一些好好的列表页面发生 :Expression # of SELECT list is not in GROUP BY clause and contains no ...
- 排序算法--希尔排序(Shell Sort)_C#程序实现
排序算法--希尔排序(Shell Sort)_C#程序实现 排序(Sort)是计算机程序设计中的一种重要操作,也是日常生活中经常遇到的问题.例如,字典中的单词是以字母的顺序排列,否则,使用起来非常困难 ...
- mysql按位的索引判断值是否为1
DELIMITER $$ DROP FUNCTION IF EXISTS `value_of_bit_index_is_true`$$/*计算某个数字的某些索引的位的值是否都为1,索引类似1,2,3, ...
- 用lua编写wireshark插件分析自己定义的协议
参见: https://yoursunny.com/study/IS409/ScoreBoard.htm https://wiki.wireshark.org/LuaAPI/TreeItem http ...