之前,我们学习过如何使用生成函数来做一些组合问题(比如背包问题),但是它面对排列问题(有标号)的时候就束手无策了。

究其原因,是因为排列问题的递推式有一些系数(这个待会就知道了),所以我们可以修改一下生成函数的式子。


对于数列$\{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)学习笔记的更多相关文章

  1. FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅲ

    第三波,走起~~ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅱ 单位根反演 今天打多校时 1002 被卡科技了 ...

  2. FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅱ

    因为垃圾电脑太卡了就重开了一个... 前传:多项式Ⅰ u1s1 我预感还会有Ⅲ 多项式基础操作: 例题: 26. CF438E The Child and Binary Tree 感觉这题作为第一题还 ...

  3. 操作系统学习笔记(五)--CPU调度

    由于第四章线程的介绍没有上传视频,故之后看书来补. 最近开始学习操作系统原理这门课程,特将学习笔记整理成技术博客的形式发表,希望能给大家的操作系统学习带来帮助.同时盼望大家能对文章评论,大家一起多多交 ...

  4. DBus学习笔记

    摘要:DBus作为一个轻量级的IPC被越来越多的平台接受,在MeeGo中DBus也是主要的进程间通信方式,这个笔记将从基本概念开始记录笔者学习DBus的过程 [1] DBus学习笔记一:DBus学习的 ...

  5. OpenCV之Python学习笔记

    OpenCV之Python学习笔记 直都在用Python+OpenCV做一些算法的原型.本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段.现在看 到一本国外的新书< ...

  6. javascript - 浏览TOM大叔博客的学习笔记

    part1 ---------------------------------------------------------------------------------------------- ...

  7. ajax跨域请求学习笔记

    原文:ajax跨域请求学习笔记 前言 ajax,用苍白的话赞扬:很好. 我们可以使用ajax实现异步获取数据,减少服务器运算时间,大大地改善用户体验:我们可以使用ajax实现小系统组合大系统:我们还可 ...

  8. Underscore.js 源码学习笔记(下)

    上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...

  9. Beego学习笔记

    Beego学习笔记 Go 路由(Controller) 路由就是根据用户的请求找到需要执行的函数或者controller. Get /v1/shop/nike ShopController Get D ...

随机推荐

  1. SNF软件开发机器人-子系统-功能-数据列表分页与不分页-瀑布式分页-如何配置?

    [列表]分页 1.效果展示: (1)不分页 (2)普通分页 (3)瀑布式分页 2.使用说明: 打开显示页面,点击开发者选项的简单配置按钮.在功能表信息中选择需要的分页方式.普通分页和瀑布式分页需要配合 ...

  2. 关于python中pika模块的问题

    工作中经常用到rabbitmq,而用的语言主要是python,所以也就经常会用到python中的pika模块,但是这个模块的使用,也给我带了很多问题,这里整理一下关于这个模块我在使用过程的改变历程已经 ...

  3. 2D空间的OBB碰撞实现

    OBB全称Oriented bounding box,方向包围盒算法.其表现效果和Unity的BoxCollider并无二致.由于3D空间的OBB需要多考虑一些情况 这里仅关注2D空间下的OBB. 实 ...

  4. 在.txt文件的首行写上.LOG后,后面每次对改文本文件进行编辑后,系统会自动在编辑内容后记录操作时间

    在.txt文件的首行写上.LOG后,后面每次对改文本文件进行编辑后,系统会自动在编辑内容后记录操作时间

  5. Delphi XE以后的版本 程序如何瘦身

    第一步:关闭debug infomation. 打开工程后,依次点击project--option--delphi compiler--linking 将右边Debug information改为Fa ...

  6. 如何在phpstorm中查看yaf框架源码

    1.到github下载yaf框架的doc 下载链接 https://github.com/elad-yosifon/php-yaf-doc/archive/master.zip 2.解压zip包 3. ...

  7. vue2.0 技巧汇总

    /** * Created by */ export default { trim: (str) => { //删除左右两端的空格 return str.replace(/(^\s*)|(\s* ...

  8. jQuery左侧图片右侧文字滑动切换代码

    分享一款jQuery左侧图片右侧文字滑动切换代码.这是一款基于jQuery实现的列表图片控制图片滑动切换代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div cla ...

  9. stl综合

    区别: List封装了链表,Vector封装了数组, list和vector得最主要的区别在于vector使用连续内存存储的,他支持[]运算符,而list是以链表形式实现的,不支持[]. Vector ...

  10. clientHeight scrollHeight offsetHeight

    <div  style="height:200px;padding:10px;border:1px solid green;"></div> 对于上面的di ...