多项式与生成函数
高数基础补档
复数相关
棣莫弗定理:
欧拉公式:
也就是
单位根:对于
单位根有以下性质:
-
折半引理:
,由我们上面推导的通项公式即可证明. -
消去引理:
,同样使用通项,运用三角恒等变换可证明.
分圆多项式
上复平面,设
不难发现
我们不妨定义
事实上,我们令
接下来观察
接下来考虑拿到
Example(尺规做正n边形问题)
碰瓷高斯问题.
一步一步来.根据尺规作图理论:尺规作图只可以实现
正五边形问题
观察正五边形在复平面上的图像,注意到有两对点互为共轭复数,我们令:
不难验证:
可以求出复合条件的解,将
于是我们显然可以求得.
正七边形
类似正五边形,最后会导出三次方程:根中含有三次根号,因此不行.
正n边形
要解决正17边形,只需要解决正n边形,然后令n=17即可.
你问我咋想到的下面的证明?问高斯去.
下面其实用到了ntt知识,但我懒得扔下面了.
先假设
我们有
我们现在想要这么分组:
泰勒展开
即
麦克劳林展开是生成函数的基础,我们所谓的生成函数的封闭形式其实就是麦克劳林展开的逆运算(可能也不能完全等价,但笔者能力不够,暂且这么理解).
离散傅里叶变换
考虑如何将一个交换群以一个函数(未必是同态)映射到复数,则这个东西天然对
结构定理首先给出
现在考虑这个映射本身所组成的交换群
. . . .
考虑在上面的复内积
. .
接下来定义卷积
还是应该再思考一下两者的地位,见到:
. . . .
考虑(1),显然
(2)(3)显然.
对于(4):
对于(5):
多项式
多项式基础
点值表示法和系数表示法
代数基本定理:一个
定理:一个
证明:考虑反证法,假设命题不成立,则存在两个
令
由上面的内容,多项式有点值表示法和系数表示法两种:
系数表示法:
点值表示法:
已知多项式点值表示法求系数表示法的过程被称为插值.
拉格朗日插值
构造多项式
另外,如果
多项式运算
考虑两个多项式相乘,如果我们已知他们的点值表示法,显然可以直接相乘.
这为我们提供了一种思路:先将系数表示法转化为点值表示法,进行相乘之后再转化回系数表示法.
这引出以FFT为代表的多项式乘法,并拓展到了多种多项式运算.
多项式乘法
快速傅里叶变换(FFT)
DFT
将
如果朴素带入,复杂度显然不可接受.
考虑:
令
A_2(x)=\sum_{i=2k+1,k\in\mathbb{N}}{n-1}a_ix{k}\
接下来分类讨论:
根据折半引理:
这样我们处理完了前半部分.
根据消去引理:
综上,我们可以递归处理
IDFT
设
我们求出
当
否则根据等比数列求和公式,
所以
那么我们有
a_k=\frac{c_k}{n}\
写法
递归写法显然.
递归过程中,第
(没找到fft的代码,懒得写了,直接用的ntt的,注意快速幂要处理幂为负数的情况).
inline void init_rev(int limit,int k){
rev[0]=0;
for(int i=1;i<limit;++i){
rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
}
return ;
}
inline void ntt(int limit,ll *a,ll t){//DFT:t=1;IDFT:t=-1
for(int i=0;i<limit;++i){
if(i<rev[i])std::swap(a[i],a[rev[i]]);
}
for(int mid=1;mid<limit;mid=mid<<1){
int n=mid<<1;
ll wn=mpow(gn,t*(mod-1)/n);
for(int i=0;i<limit;i+=n){
ll w=1;
for(int k=0;k<mid;++k,w=w*wn%mod){
ll wakn=w*a[i+k+mid]%mod;
ll ak=a[i+k];
a[i+k]=ak+wakn;Mod(a[i+k]);
a[i+k+mid]=ak-wakn+mod;Mod(a[i+k+mid]);
}
}
}
if(t==-1){
ll inv=mpow(limit,-1);
for(int i=0;i<limit;++i){
a[i]=a[i]*inv%mod;
}
}
return ;
}
快速数论变换(NTT)
由于FFT中的单位根会产生精度误差,因此在膜
NTT与FFT的运算过程基本相同,证明过程基本相同,唯一不同的是将单位根改为了原根.
根据上面FFT的证明过程,我们知道,设原根为
-
且 ,证明由原根的性质. -
折半引理:
,证明显然. -
消去引理:
.由于 ,该结论显然成立.
由上我们证明了,我们完全可以使用
另外,注意到
范德蒙德矩阵理解
范德蒙德矩阵形如:
如果取单位根,我们有:
这就是我们在做FFT(一个线性变换)的时候的变换矩阵.所以我们有:
分治FFT
给定
考虑分治,假如我们已经知道了
这显然是一个卷积的形式,我们直接计算
多项式求逆
对于多项式
首先不妨设
如果我们已知
两边平方:
两边乘一下
根据主定理,这么做复杂度是
同时,多项式求逆可以解决上面提到的分治FFT.我们注意到分治FFT的条件等价于:
于是可以直接做多项式求逆.
多项式除法
对于
考虑对于
那么我们有:
于是只要做一遍多项式求逆即可求得
多项式ln
给出
我们有:
另外,考虑中间求导的过程中,其实模数也要相应发生变化,但是由于模数是从更高次变低,而最后积分的时候又要变回来,所以可以直接忽略变化.
定理:在模意义下当且仅当
我们对最后再做一步:
首先
若
牛顿迭代
给定多项式
首先
假设我们已经求出了在
考虑
于是我们有:
牛顿迭代可以用来证明多项式求逆的式子同样正确.
多项式开方
给定
根据牛顿迭代,有:
还没完,用牛顿迭代前一定要求
多项式exp
给定
根据牛顿迭代,有:
还没完,还需要求
多项式快速幂
求
我们有一个结论:
这个结论很简单,注意到
而又由于
多项式运算全家桶(重载运算符版)
我们必须指出的一点是,虽然重载运算符很好看,但是大部分情况下还是需要指针传参.例如在这里,由于做
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define qwq 300007
const int mod=998244353;
const int gn=3;
int n;
int rev[qwq];
ll inv[qwq];
struct poly{
ll x[qwq];
int limit,k;
}a;
inline void Mod(ll &x){
if(x>=mod)x-=mod;
if(x<0)x+=mod;
return ;
}
inline ll mpow(ll x,ll k){
if(k<0)k+=mod-1;
ll ans=1;
for(;k;k=k>>1,x=x*x%mod){
if(k&1)ans=ans*x%mod;
}
return ans;
}
inline ll get_inv(int x){
if(x>qwq-7)return mpow(x,mod-2);
if(inv[x])return inv[x];
else return inv[x]=mpow(x,mod-2);
}
inline void init_rev(int limit,int k){
for(int i=1;i<limit;++i){
rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
}
return ;
}
inline void ntt(poly *a,int t){
init_rev((a->limit),(a->k));
for(int i=0;i<(a->limit);++i){
if(rev[i]>i)std::swap(a->x[i],a->x[rev[i]]);
}
for(int mid=1;mid<(a->limit);mid=mid<<1){
int n=mid<<1;
ll wn=mpow(gn,t*(mod-1)/n);
for(int i=0;i<(a->limit);i+=n){
ll w=1;
for(int k=0;k<mid;++k,w=w*wn%mod){
ll wakn=w*(a->x[i+k+mid])%mod;
ll ak=(a->x[i+k]);
a->x[i+k]=ak+wakn;Mod(a->x[i+k]);
a->x[i+k+mid]=ak-wakn;Mod(a->x[i+k+mid]);
}
}
}
if(t==-1){
ll inv=get_inv(a->limit);
for(int i=0;i<a->limit;++i){
a->x[i]=(a->x[i])*inv%mod;
}
}
return ;
}
inline poly operator %(poly x,int k){//对x^{2^k}取膜
for(int i=(1<<k);i<x.limit;++i)x.x[i]=0;
x.k=k;
x.limit=1<<k;
return x;
}
inline poly operator +(poly x,poly y){
x.limit=std::max(x.limit,y.limit);
x.k=std::max(x.k,y.k);
for(int i=0;i<x.limit;++i){
x.x[i]+=y.x[i];Mod(x.x[i]);
}
return x;
}
inline poly operator -(poly x,poly y){
x.limit=std::max(x.limit,y.limit);
x.k=std::max(x.k,y.k);
for(int i=0;i<x.limit;++i){
x.x[i]-=y.x[i];Mod(x.x[i]);
}
return x;
}
inline poly operator*(ll x,poly y){
for(int i=0;i<y.limit;++i){
y.x[i]=x*y.x[i]%mod;
}
return y;
}
inline poly operator *(poly x,poly y){
x.limit=std::max(x.limit,y.limit)<<1;
x.k=std::max(x.k,y.k)+1;
y.limit=x.limit;y.k=x.k;
ntt(&x,1);ntt(&y,1);
for(int i=0;i<x.limit;++i){
x.x[i]=x.x[i]*y.x[i]%mod;
}
ntt(&x,-1);
return x;
}
poly q_inv,tmp_inv;
inline poly invpoly(poly x){
for(int i=0;i<(x.limit<<1);++i){
q_inv.x[i]=tmp_inv.x[i]=0;
}
q_inv.x[0]=mpow(x.x[0],-1);q_inv.limit=1,q_inv.k=0;
for(int lim=2,k=1;lim<=x.limit;lim=lim<<1,++k){
for(int i=0;i<lim;++i){
tmp_inv.x[i]=x.x[i];
}
tmp_inv.limit=q_inv.limit=lim;
tmp_inv.k=q_inv.k=k;
q_inv=2ll*q_inv-q_inv*q_inv%k*tmp_inv%k;
}
q_inv.limit=x.limit;q_inv.k=x.k;
return q_inv;
}
inline poly operator /(poly x,poly y){
int lim=x.limit,k=x.k;
x=x*invpoly(y);
for(int i=lim;i<x.limit;++i){
x.x[i]=0;
}
x.limit=lim,x.k=k;
return x;
}
inline poly Dpoly(poly x){//求导
for(int i=1;i<x.limit;++i){
x.x[i-1]=x.x[i]*i%mod;
x.x[i]=0;
}
return x;
}
inline poly Spoly(poly x){//积分
for(int i=x.limit-1;i>=0;--i){
x.x[i+1]=x.x[i]*get_inv(i+1)%mod;
x.x[i]=0;
}
return x;
}
inline poly lnpoly(poly x){
if(x.x[0]!=1);//无解
return Spoly(Dpoly(x)/x);
}
poly q_exp,tmp_exp;
inline poly exppoly(poly x){
if(x.x[0]!=0);//无解
for(int i=0;i<(x.limit<<1);++i){
q_exp.x[i]=tmp_exp.x[i]=0;
}
q_exp.x[0]=1;
for(int lim=2,k=1;lim<=x.limit;lim=lim<<1,++k){
for(int i=0;i<lim;++i)tmp_exp.x[i]=x.x[i];
tmp_exp.limit=q_exp.limit=lim;
tmp_exp.k=q_exp.k=k;
q_exp=(q_exp+q_exp*(tmp_exp-lnpoly(q_exp)))%k;
}
return q_exp;
}
poly q_sqrt,tmp_sqrt;
inline poly sqrtpoly(poly x){
if(x.x[0]!=1);//如果不是1要做二次剩余
q_sqrt.x[0]=1;
for(int lim=2,k=1;lim<=x.limit;lim=lim<<1,++k){
for(int i=0;i<lim;++i)tmp_sqrt.x[i]=x.x[i];
tmp_sqrt.limit=q_sqrt.limit=lim;
tmp_sqrt.k=q_sqrt.k=k;
q_sqrt=(q_sqrt*q_sqrt%k+tmp_sqrt)/(2ll*q_sqrt)%k;
}
return q_sqrt;
}
inline poly powpoly(poly x,ll k){
if(x.x[0]!=1);//无解
x=k*lnpoly(x);
return exppoly(x);
}
集合幂级数
集合幂级数形如
下述级数如无特别说明均为集合幂级数.
与/或卷积
高维前缀和:
高维后缀和:
上述过程又称快速莫比乌斯变换(FMT).
for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if(j&(1<<i)) a[j]+=a[j^(1<<i)];//高维前缀和
for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if(j&(1<<i)) a[j^(1<<i)]+=a[j];//高维后缀和或卷积:
与卷积:
二者求法类似,考虑如何求
引理:
若
若
设
现在考虑已知
于是有
因而做两遍高维前缀和再反推回去即可,复杂度
与卷积即改为高维后缀和.
inline void FWT_and(ll *a,ll t,int limit){//FWT:t=1;IFWT:t=-1
for(int mid=1;mid<limit;mid=mid<<1){
int n=mid<<1;
for(int j=0;j<limit;j+=n){
for(int k=0;k<mid;++k){
a[j+k]+=t*a[j+k+mid]%mod;
Mod(a[j+k]);
}
}
}
return ;
}
inline void FWT_or(ll *a,ll t,int limit){//FWT:t=1;IFWT:t=-1
for(int mid=1;mid<limit;mid=mid<<1){
int n=mid<<1;
for(int j=0;j<limit;j+=n){
for(int k=0;k<mid;++k){
a[j+k+mid]+=t*a[j+k]%mod;
Mod(a[j+k+mid]);
}
}
}
return ;
}
异或卷积
异或卷积:
引理:
证明的话考虑如果
快速沃尔什变换(FWT):
定义集合幂级数
那么有:
时间复杂度
逆运算的话考虑实现过程,反向就行.不过可以把过程中乘上的
FMT可以看作是FWT在解决与/或卷积时的特例.
inline void FWT_xor(ll *a,ll t,int limit){//FWT:t=1;IFWT:t=-1
for(int mid=1;mid<limit;mid=mid<<1){
int n=mid<<1;
for(int j=0;j<limit;j+=n){
for(int k=0;k<mid;++k){
ll x=a[j+k],y=a[j+k+mid];
a[j+k]=x+y;Mod(a[j+k]);
a[j+k+mid]=x-y;Mod(a[j+k+mid]);
}
}
}
if(t==-1){
ll inv=mpow(limit,mod-2);
for(int i=0;i<limit;++i)a[i]=a[i]*inv%mod;
}
return ;
}
快速沃尔什变换
线性代数角度
我们来重定义一下所谓的FWT.
首先类比FFT,我们希望存在一个线性变换
-
若
,则 . -
这个线性变换是可逆的.
-
做这个线性变换和其逆变换的复杂度都可以接受.
我们设
考虑:
再考虑:
比较两边系数,有
而由于
这样我们就可以只求一个
考虑和FFT一样折半,令
这样就实现了规模减半,复杂度
下面我们设FWT的变换矩阵为
或卷积
取矩阵
与卷积
取矩阵
异或卷积
取矩阵
生成函数角度
我们再从生成函数角度理解一下FWT.
我们重新定义幂乘法:
观察FWT的式子:
这等价于:
子集卷积
子集卷积:
意识到该卷积与或卷积的差别在于:或卷积会多累加一些
因而可以将原集合按照元素个数分组做FMT,然后再
集合占位幂级数
其实就是设
Example
Example1([AGC034F] RNG and XOR)
设
注意到
接下来涉及到的东西就很本质了,我们一开始先把
(我草这个式子太顶级了)
但是我们冷静一下,这个题与普通生成函数不同的地方在于,我们要求
然后大概做做吧,感觉太顶级了.
Example2([QOJ5089]环覆盖)
合法显然当且仅当每个点度数为偶数,考虑直接拿一个二进制数将每个点度数奇偶性压起来,如果选中一条边
仔细想想这个过程:有一句名言是只要看到生成函数就一定存在分配律,这里也是一样的,由于存在一种选择:选不选这条边,因此这里也就有了两种情况:
问题在于对于每个
Example3(CF1034E Little C Loves 3 III)
仍然是子集卷积,转化为
还有个用到FWT的本质的矩阵做法,大概是手推矩阵然后再手推求逆.
Example4(CF1336E2 Chiori and Doll Picking)
先考虑easy version.首先求出线性基,如果线性基的大小
那么我们怎么优化呢?首先
考虑设
然后接下来开始一波顶级操作(下面的操作全部基于行向量+行操作):
引理1:
考虑:
这句是为啥呢?因为对于右边的每一个数字
于是我们有:
引理2:
直接展开上面的式子,用
引理3:
只需要证明封闭性就好,注意到如果
引理4:
设这些位置构成的空间是
注意到
引理5:将
首先根据
通过这个引理可以由
引理6:
因为
然后注意到
接下来怎么做呢?令
太顶级了吧.
Example5(CF 1326F2)
首先发现"如果没有边那么是
但是还没完,题目让我们求每一个,我们不难发现我们这样划分之后答案只取决于链的长度的可重集合,而本质不同的集合的数量很少,直接枚举就行.
Example6(qoj5019)
首先可以类似数位dp设计一个
这个题知道题解其实没什么难的,但是这个题告诉了我们:FWT作为一种线性变换,它是可以和其它线性变换一起做的,也就是说你是可以将其中的若干位做FWT,剩下若干位做其它的东西的.
生成函数
普通生成函数(OGF)
概念
我们定义一个幂级数形如
运算
-
. -
. -
. -
. -
. -
. -
.
常见序列生成函数
, .
证明显然.
, .
证明根据二项式定理.
.
证明显然.
,
直接使用二项式定理展开
反转上指标并使用对称恒等式得到上式.此外上式还有两个特殊形式:
根据
-
. -
. -
.
可以使用积分或泰勒展开证明.
.
也即卡特兰数
然后得到两个根,带入
指数生成函数(EGF)
https://zhuanlan.zhihu.com/p/53079223
序列
基本运算
我们有:
即
注意到有一个特例是
封闭式
直接泰勒展开就可以得到
换元后可以得到.一个经典特例是
.
显然.
.
做二项式定理就显然了.
-
. -
.
都可以通过泰勒展开证明.
EXP的组合意义
我们设
设
或者直接递推:
简而言之,
Example
Example1(POJ3734)
对于红黄色砖块,其选取方案为
对于蓝绿色砖块,选取方案是
乘起来有:
于是有
Example2(圆排列)
长度为
长度为
于是有
这个怎么理解呢?考虑一个排列可以分成若干个置换环,而一个集合能形成的置换环数量显然就是圆排列.
Example3(错排数)
从置换环的角度考虑,错排是指置换环中不存在自环的排列,也就是说不存在长度为
Example4(点带编号无向连通图计数)
考虑如果
Example5(不动点计数)
求有多少个映射
考虑将
Example6([CF891E]Lust)
假设
考虑对所有情况下的
答案就是
Example7
狄利克雷生成函数(DGF)
对于序列
基本运算
对于两个序列
封闭式
.
显然为
.
其封闭式是黎曼函数
.
其DGF为
.
有
.
.
注意到:
也注意到
.
注意到
.
Example
Example1(luoguP3768)
考虑对于
注意到:
也就是
阶乘的扩展定义
对于复数的阶乘,我们通常定义:
同时我们定义
这样我们还可以定义广义阶乘幂:
通过以上我们还可以有二项式系数的定义:
超几何级数
超几何函数
我们定义超几何函数
许多生成函数都可以写成超几何函数的形式.
值得一提的是,如果我们直接定义类似
值得一提的是,我们通常直接忽略超几何函数中的任何特殊情况,例如分母为
特殊的超几何函数
合流超几何函数
我们通常把形如
不难发现我们有:
也即常见生成函数中的
高斯超几何函数
我们把形如
下面是几种常见的高斯超几何函数形式:
.
即常见生成函数
.
即常见生成函数
.
即常见生成函数
.
即常见生成函数
超几何级数的应用
我们先考虑改写超几何级数的形式:
不难发现
换句话说,
换句话说,对于一个无穷级数
那么有:
好,接下来请把脑子扔了,不要纠结某一个公式按理来说只能作用于正整数而在这里直接将它套在了复数域上.你只需要知道数学家非常厉害通过扩展一些东西的定义(大部分是阶乘和
Example
求证:
首先考虑:
有了这个无穷级数,我们可以直接将二项式系数用阶乘形式展开,于是得到:
两边同时除以
二项式系数与超几何函数
通过范德蒙德卷积,不难验证:
这个公式的一个特例是:
这个公式几乎囊括了所有的基本二项式求和公式:上指标求和,平行求和,范德蒙德卷积......几乎只要是我们推出的由两个及以下项的乘积求和的二项式系数相关的式子都可以使用这个式子.
那么如果我们要三项相乘或更多,我们有Saalschütz恒等式:
事实上,《具体数学》上给出了大量的超几何级数的应用以及各种技巧,但是笔者的智商已经理解不了接下来的内容了.考虑到大部分时候上述两个超几何级数恒等式已经足够解决绝大部分问题,如果考到更加困难的求和技巧,笔者相信别人也不会做,于是笔者决定摆烂.
求微分方程
Example1(luogu4931)
二项式反演:
注意到后者只与
加强版咋做?我们继续看看式子:
注意到
考虑
再看
这下简单了,答案是:
现在看
两边求导:
得到了一个线性递推形式,更进一步地:
技术总结一下:其实就是你想要得到一个递推式,然后注意到这玩意要写成微分方程的形式,所以开始往那边凑.
生成函数的应用
求解递归关系
我们假设已经有了
考虑有理函数
那么可以证明,只要
我们这么定义"反射"运算,若
若
那么显然这里求出来的这组数
而我们有
Example1
已知
首先两边同时除以
如果我们令
于是
Example2([QOJ5169] 夹娃娃)
首先设
问题在于每次询问的时候求出答案呢?
这里有一个套路:我们在一开始就暴力做点值,最后拿拉格朗日插值求答案.中间大概把能预处理的都预处理一下.最后的问题在于:
第一,预处理点值的时候,一共有
第二,询问的时候需要找到所有对应的点值并暴力乘起来,复杂度来到
第三,拉格朗日插值的时候需要
Example3([十二省联考 2019] 皮配)
首先注意到题目等价于规定一个阵营和一个排序的人数上下界.
我们可以将这四位导师分别记为
注意到如果没有学校有偏好,将生成函数卷起来后得到的答案就是
对于那些有偏好的学校,我们暴力算就行.复杂度不会高于
评论