指数型生成函数(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 ...
随机推荐
- C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别
C#语法——泛型的多种应用 本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...
- mysql视图详解 mysql视图
目录 22.1. ALTER VIEW语法 22.2. CREATE VIEW语法 22.3. DROP VIEW语法 22.4. SHOW CREATE VIEW语法 本章讨论了下述主题: · ...
- Mysql Binlog三种格式详细介绍
一.MySQL Binlog格式介绍 mysql binlog日志有三种格式,分别为Statement,MiXED,以及ROW! 查看binlog的格式的脚本: 二.binlog 的不同模式有什么区别 ...
- python 大数据处理小结
1.shop_min=shop.drop(['category_id','longitude','latitude','price'],axis=1)pandas中删除多个列 2.mall=shop_ ...
- 浏览器神器--vimium
自从学会了正确的坐姿,坐在电脑一整天腰也不酸了.背也不痛了,精神倍棒吃嘛嘛香 zuomeng.png 但奈何使用鼠标久了,手腕.肩膀依旧疼痛.偶尔逛知乎,看到有人推荐chrome浏览器的vimiu ...
- 当 return 遇到 try
. . . . . 今天有同事和我探讨在群里看到的一道有趣的题目,在探讨的过程中让我搞清楚了一些曾经模糊的概念,特此记录下来. 题目给出如下代码,问运行后打印的结果是什么. public static ...
- 力导向图Demo
<html> <head> <meta charset="utf-8"> <title>力导向图</title> < ...
- HIVE metastore Duplicate key name 'PCS_STATS_IDX' (state=42000,code=1061)
HDP 版本:2.4.0.0-169. 解决:将hive 所在 节点上的/usr/hdp/2.4.0.0-169/hive/script/metastore/upgrade/msql/hive-sch ...
- 开始学习Functional Programming
打算先学F#, 再学Scala. 第一个F#程序 open System [<EntryPoint>] let main argv = let a = "Hello, World ...
- python faker 生成随机类型字符串
以前生成测试字符时,用random模块拼来拼去来生成随机串,如姓名,手机,身份证等,还是费一些功夫,不过有了faker模块,一切变得简单起来 基本使用: from faker import Faker ...