指数型生成函数(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 ...
随机推荐
- CentOS 7.5 安装KVM虚拟机(Linux)
1.认识理解KVM虚拟机 Kernel-based Virtual Machine的简称,是一个开源的系统虚拟化模块,自Linux 2.6.20之后集成在Linux的各个主要发行版本中.它使用Linu ...
- 关于Java 软件工程师应该知道或掌握的技术栈
鄙人星云,今天突然想写这么一篇需要持续更新的文章,主要目的用于总结当前最流行的技术和工具,方便自己也方便他人. 更新时间:2018-10-23 09:26:19 码农职业路径图 码农入门职业路径图 J ...
- [moosefs] storage class
chapter 1 moosefs 3.1 storage class 功能的介绍 1.1 什么是storage class 在moosefs中,storage class允许指定文件的chunks存 ...
- Linux好用的工具命令 - rz/sz
Linux系统中将本地的文件上传到服务器或者从服务器上下载文件到本地可以通过安装lrzsz工具来简单便利的完成. >>RZ :将本地的文件上传到服务器 >>SZ:从服务器上下载 ...
- mongodb常用查询语法
一.查询 find方法 db.collection_name.find(); 查询所有的结果: select * from users; db.users.find(); 指定返回那些列(键): se ...
- tun/tap设备_虚拟网卡
tun/tap 驱动程序实现了虚拟网卡的功能,tun表示虚拟的是点对点设备,tap表示虚拟的是以太网设备,这两种设备针对网络包实施不同的封装.利用tun/tap 驱动,可以将tcp/ip协议栈处理好的 ...
- 记一次wordpress安装过程中遇到的问题及解决办法
Q:无法建立目录wp-content/uploads/2017/03.有没有上级目录的写权限?A:执行chmod 777 wp-content/ 提升目录权限 Q:安装主题或安装插件的时候,用到FTP ...
- 当我们用自定义的类对象作为key时,我们必须在程序中覆盖HashCode()和equals()。
Key值既然可以作为对象,那么也可以用一个自定义的类.比如: m.put(new sutdent(“Liucy”,30),”boss”) 如果没有语句来判定Student类对象是否相同,则会全部打印出 ...
- PHP正则验证类
项目中经常使用到的验证,很使用的.查看与下载<?php /** * 验证类 * * @lastmodify 2015-12-19 * @author wuheng */ class Verify ...
- elasticsearch以及head插件在centos7上的安装与配置教程
ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为Apach ...