[复习]多项式和生成函数相关内容

Posted cjyyb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[复习]多项式和生成函数相关内容相关的知识,希望对你有一定的参考价值。

[复习]多项式和生成函数相关内容

多项式

涉及的方面

主要在于多项式的乘法,也就是(FFT,NTT,MTT)
但是也多项式的求逆,(exp)(ln),开根,求导,积分等操作。

多项式乘法

并没有什么好复习的,记好板子就行了。同样也是多项式运算的基础。

泰勒展开&麦克劳林级数

泰勒展开:
如果(f(x))(x0)处存在(n)阶导,那么就有:
[egin{aligned}f(x)&=f(x0)+frac{f^1(x0)}{1!}(x-x0)+frac{f^2(x0)}{2!}(x-x0)^2+...+frac{f^n(x0)}{n!}(x-x0)^n+xi\\&=sum_{i=0}^n frac{f^i(x0)}{i!}(x-x0)^i+xiend{aligned}]
其中(xi)是余项,当(n)趋近于无穷大的时候,(xi)趋近于高阶无穷小。

(x0=0)时是特殊情况下的泰勒展开,称之为麦克劳林级数。此时((x-x0)^i)项变为了(x^i)
最常见的就是(e^x)的展开
[e^x=1+frac{x}{1!}+frac{x^2}{2!}+frac{x^3}{3!}+...]

牛顿迭代

多项式相关操作,除了直接计算的求导和积分之外,基本使用牛顿迭代进行计算,这样子即使不记得也可以很好的手玩出来。
首先我们知道多项式的任何一个运算(求逆,开方,(exp)(ln)),都可以简单的写成对于多项式(B(x)),以及给定函数(F(x)),求解(F(B(x))equiv 0(mod x^{2^t}))
(B_t(x))表示的一个合法解,在(t=0)时,只剩下常数项,我们可以利用常数项的运算很容易的求解出(B_0)的值,现在考虑如何用(B_t)推出(B_{t+1}),这样子我们就可以用(B_0)推出答案。
对于(F(B_{t+1}(x)))(B_t(x))处进行泰勒展开,我们得到:
[F(B_{t+1}(x))=F(B_t(x))+frac{F'(B_t(x))}{1!}(B_{t+1}(x)-B_t(x))]
后面的项数因为在模(x^{2^{t+1}})意义下为(0),所以只有前面两项。
那么化简之后我们就可以得到递推式(注意左侧的(F(B_{t+1}(x)))在模意义下为(0)):
[B_{t+1}(x)=B_t(x)-frac{F(B_t(x))}{F'(B_t(x))}]

多项式运算

求导与积分

很容易,高中数学课上都会讲的东西。
记住求完导之后最高项为(0),积分后常数项为(0)

多项式求逆

其实可以手玩(抛开牛顿迭代,可以自己递归推式子,虽然本质上一样的)
直接套上牛顿迭代的式子就好了。假设(A(x))是我们要求逆的多项式。
那么有:(F(B_t(x))=A_{t+1}(x)*B_t(x)-1equiv 0)
所以:
[B_{t+1}(x)=B_t(x)-frac{A_{t+1}(x)*B(x)-1}{A_{t+1}(x)}]
然后因为(B_t(x))(A_t(x))的逆,所以可以进一步化简。
[egin{aligned}B_{t+1}(x)&=B_t(x)-frac{A_{t+1}(x)*B(x)-1}{A_{t+1}(x)}\\&=B_t(x)-B(x)*(A_{t+1}(x)*B(x)-1)\\&=2B_t(x)-A_{t+1}(x)*B^2_t(x)end{aligned}]

注意给(A)带下标的意义不大,但是这里特地写出来是提醒注意下当前层的模数应该是什么。

多项式开根

直接套式子很方便,既然是开根就是:(F(B_t(x))=B^2_t(x)-A(x)=0)
推式子就是:
[B_{t+1}(x)=B_t(x)-frac{B^2_t(x)-A(x)}{2B_t(x)}=frac{1}{2}(B_t(x)-frac{A(x)}{B_t(x)})]
也就是多项式开根的过程中需要套用多项式求逆。

多项式(ln)

这个操作并不需要用到牛顿迭代。
[egin{aligned}ln(A(x))&=B(x)\\(ln(A(x))'&=B'(x)\\frac{A'(x)}{A(x)}&=B'(x)end{aligned}]
所以只需要对于(A(x))求导求逆,乘起来之后就是(B'(x)),直接积分就完事了。

多项式(exp)

这个推导稍稍复杂一点点。关系式就是(exp(A(x))=B(x)),显然这个东西没法直接算,而(exp)求导之后还有(exp),所以我们求(ln)(exp)去掉。(A(x)=ln(B(x)))
这样子也就是(F(B_t(x))=ln(B_t(x))-A(x))
牛顿迭代推式子就是:
[egin{aligned}B_{t+1}(x)&=B_t(x)-frac{ln(B_t(x))-A(x)}{frac{1}{B_t(x)}}\\&=B_t(x)(1-ln(B_t(x))+A(x))end{aligned}]

多项式快速幂

(A^k(x)=exp(k*ln(A(x))))
(emmm),虽然这东西复杂度是一个(log),但是这玩意常数巨大。

多项式除法

这个除法是带余除法,所以并不能直接求逆解决。
要求的就是给定两个多项式(A(x),B(x)),其项数为(n,m)
求解一个(n-m)项的多项式(C(x)),以及一个小于(n-m)项的多项式(R(x))
满足:(A(x)=B(x)*C(x)+R(x))
定义一个操作(R),其中(R)就是(Reverse)(A^R(x)=x^nA(frac{1}{x}))。这个操作说白点就是(A(x)[x^i])对应(A^R(x)[x^{n-i}]),也就是把所有的系数给翻转过来。
然后推式子:
[egin{aligned} A(x)&=B(x)*C(x)+R(x)A(frac{1}{x})&=B(frac{1}{x})*C(frac{1}{x})+R(frac{1}{x})x^nA(frac{1}{x})&=(x^m*B(frac{1}{x}))*(x^{n-m}*C(frac{1}{x}))+x^nR(frac{1}{x})A^R(x)&=B^R(x)*C^R(x)+R^R(x)*x^{n-m+1} end{aligned}]
到了这里我们把等号换成同余,把整个式子在模(x^{n-m+1})意义下进行。
[egin{aligned} A^R(x)&equiv B^R(x)*C^R(x)+R^R(x)*x^{n-m+1}&equiv B^R(x)*C^R(x) end{aligned}]
所以在模意义下,我们可以利用多项式求逆求解(C^R(x)=frac{A^R(x)}{B^R(x)}mod x^{n-m+1})
那么就有(R(x)=A(x)-B(x)*C(x))

生成函数

普通型生成函数(OGF)

对于数列({a_i})而言,其(OGF)(sum_{i=0}^infty a_ix^i)
和前面多项式的区别在与这个东西是无穷项。
比如说斐波那契数列,写成(OGF)的形式的话就是:
[F(x)=0+1x+1x^2+2x^3+3x^4+5x^5...]
因为是无穷项,所以我们可以推出:
[F(x)=xF(x)+x^2F(x)+x]
然后就可以把(F(x))写成和(x)相关的一个式子:
[F(x)=frac{x}{1-x-x^2}]
这样子一来我们可以用多项式求逆算出来斐波那契数列,虽然实际上递推是线性的。这个式子进一步推导可以求解斐波那契数列的通项。
但是如果递推关系与前(m)项相关,并且(m)的值不小,那么显然用类似的方法转为多项式相关的计算复杂度更加优秀。注意这里求出来的是整个数列。

指数型生成函数(EGF)

对于数列({a_i})而言,其(OGF)(sum_{i=0}^infty frac{a_i}{i!}x^i)
从上面的泰勒展开(e^x)的时候,我们似乎就见过了这个东西了呢!

两者之间的一些区别

(EGF)本质上和(OGF)是类似的,区别在于除了一个阶乘。
阶乘在计数中意为着什么呢?顺序。
那么从中,我们明白了这样一件事情:(OGF)考虑的是组合,意味着相同物品之间没有区别,而(EGF)考虑的是排列,相同之间也要考虑一个顺序关系。
所以我们可以来考虑两个例子:

1.有两种物品(A,B),其中(A)物品如果要拿,必须拿奇数个,(B)物品如果要拿,必须拿(3)的倍数个,求拿(n)个物品的方案数。

考虑的是组合,所以我们使用(OGF),对于两种物品构建(OGF),可以得到:
(A(x)=x+x^3+x^5+...)
(B(x)=1+x^3+x^6+...)
那么将两个生成函数直接乘起来,其第(n)项的值就是最终的答案。
一般来说这类题目可以化简生成函数之后乘起来,约分之后再考虑最终的结果,最后答案的计算方式也无非是两种,一种是直接算,另外一种是考虑组合意义。

2.有两种物品,(A)物品必须拿奇数个,(B)物品必须拿偶数个。然而所有被拿出来的物品之间都是有区别的,求方案数。

发现这个就是一个排列关系了,我们可以任务物品之间的区别就是拿出来的顺序。
而奇数个和偶数个的区别和前面使用(OGF)是类似的。
(A(x)=frac{1}{1!}x+frac{1}{3!}x^3+frac{1}{5!}x^5...=frac{e^x-e^{-x}}{2})
(B(x)=1+frac{1}{2!}x^2+frac{1}{4!}x^4+...=frac{e^x+e^{-x}}{2})
两个(OGF)直接乘起来就好了,注意要整理好系数,即一定要写成(frac{a}{i!}x^i)的形式,因为若干个乘起来之后的结果本质上是一个可重排列(frac{n!}{a_1!a_2!...a_k!}),这样最终的结果就是(frac{a}{i!}x^i)的系数(a)

常见题目

字符串配对匹配相关

匹配回文串

【BZOJ3160】万径人踪灭
题意:求回文子序列(强制不连续)的个数。
回文串满足的关系是关于一个回文中心对称,也就是满足:(S[x-a]=S[x+a])。不难发现下表(x-a+x+a=2x),也就意味着关于这个回文中心如果对称的话,那么他们的下标和为定制。所以可以分字符考虑,把相同字符设为(1),其他字符设为(0)。然后卷积一遍之后得到的结果就是关于当前位置对称的字符对数。

带通配符的匹配

【BZOJ4503】两个串
题意:给定两个串(S,T)(T)中有通配符,求(T)(S)中的出现次数。
如果不考虑通配符的情况,我们可以用(KMP)求解。除了(KMP)之外,还有一种多项式乘法的求解方法。
考虑什么情况下会匹配上:(forall iin[1,|T|] T[i]=S[k-1+i]),那么(T)就从(S)的第(k)个字符开始匹配上了一次。
发现(k-1)是常数,但是两个下表中(i)都是加法,没法消掉。这个容易,我们把(T)串翻转过来,就有:(T[|T|-i+1]=S[k-1+i]),下标之和为(|T|+k)为定值。因此我们只需要按照字符集考虑,每次将相同字符全部赋值(1),其他字符赋值为(0),跑多项式乘法。把所有字符的结果相加,如果某个位置上的和为(|T|),证明从这个位置开始能够匹配上(|T|)个字符,也就意味着在(S)中,从这个位置开始匹配上了一次(T)。这样子不失为一种好方法,并且每次只需要把通配符都变成(1)就完事了。但是缺点也很显然,复杂度和字符集相关,往往存在一个大常数,对于字符集很大的时候这种方法往往不适用。
实际上,我们考虑一种更加直接的方法。我们明白,只要有一个位置匹配不上,那么从这个位置开头就匹配不上(T)。如果我们给字符集里面的字符编号为(1..26)(字符更大就继续往下去)。如果两个不匹配的话,证明其编号不同,证明其编号的差不为(0)。所以我们只需要考虑从每个位置开始往后的字符的差的绝对值的和,如果这个值是(0)的话,显然不存在没有匹配上的位置,也就是匹配上了。当然,绝对值很麻烦,我们直接平方就好了。
因此,对于卷积完的第(k)位置,其值为(f(k)=sum_{i=1}^{|T|}(S[k+i-1]-T[i])^2)
这个式子直接拆开就变成了(sum_{i=1}^{|T|}(S[k+i-1]^2+T[i]^2-2T[i]S[k+i-1]))
发现是三个部分的和。也就是两个可以直接计算的常数项加上一个卷积的形式(别忘了后面那个东西怎么卷,上面讲过一次的)。
现在加上通配符,显然通配符也要编一个号,但是因为通配符不可能同时等于字符集里面的所有值,因此我们需要考虑一个额外的方法,使得上述式子的结果只要出现了通配符就为(0)。那么这个也很简单,令通配符编号为(0),然后在(f(k))的式子中额外乘上一个(T[i])就好了,只要(T[i])是通配符,也就是(0),那么这一项也就变成了(0),表示匹配上了。
那么式子变成了:(f(k)=sum_{i=1}^{|T|}(S[k+i-1]-T[i])^2T[i])
拆开之后也就变成了:(sum_{i=1}^{|T|}(T[i]S[k+i-1]^2+T[i]^3-2T[i]^2S[k+i-1]))
变成了两个卷积加上一个常数的形式,一样的直接解决即可。

类似题:【BZOJ4259】残缺的字符串
这道题里面(S)也包含了通配符,因此在(f(k))的式子中额外乘一个(S[i])就好了。
本质上完全一样的两道题目。另外一点,就是关注字符集的大小,对于很小的字符集而言暴力是可行的。

总结

对于这一类字符串匹配的题目,回文直接乘,顺序匹配翻转后乘,利用匹配的式子考虑倒地应该怎么乘。同时,根据要匹配的东西,考虑是分字符集还是直接编号,如果直接编号应该怎么考虑统配分等匹配问题。想清楚如何给多项式赋值,最终乘出来的是什么东西就好了。

加速计算

也就是说,在这一类问题里面,多项式的乘法并不占主要地位,主要用它来加速乘法。
比如计算(10^5)位的高精度乘法,显然就要使用(FFT),而不能暴力(O(n^2))的乘法。
还有比较直接的题目,比如【BZOJ4827】礼物【BZOJ3527】力。这两个题目都是推导式子时候发现要计算特定的一项,但是直接暴力算很慢,发现可以用(FFT)优化。
还有比较常见的是第一类斯特林数和第二类斯特林数等一些数列的快速计算问题。
比如【BZOJ4555】求和,推出用第二类斯特林数的计算式之后,唯一需要考虑的就是求第二类斯特林数,而(O(n^2))的递推太慢,因此直接用(FFT)计算。
还有像(dp)太慢所以转成多项式乘法的【BZOJ3992】序列统计
这类问题中的(FFT)只是一个优化复杂度的工具而已,并不是考察的重点(虽然如果你推不出多项式的式子的话根本做不出来的说)。一般而言都可以比较容易的写出一个复杂度在(O(n^2))的东西,然后深思熟虑看出来多项式卷积之后优化到了(O(nlogn))

生成函数相关运用

这一类问题就多得去了,要凭借的主要还是自己的脑子。

生成图的计数

注意有标号和没标号的区别,这决定了使用(OGF)还是(EGF)
想清楚生成图之间的边和点的关系,因为生成图计数问题中,点的数量显然是已知的,所以我们需要考虑的是边的情况。

【BZOJ3456】城市规划
题意:求(n)个点无向连通图的个数
显然(n)个点是有标号的,因此我们最终构建出来的生成函数一定是(EGF)
(n)个点的图的个数为(g(n)=2^{C_n^2})。设(n)个点的无向连通图的个数为(f(n))
考虑这两者之间的关系,我们可以推出式子(g(n)=sum_{i=1}^{n}C_{n-1}^{i-1}f(i)g(n-i))
可以想想这种关系是怎么来的,我们枚举(1)号点所在的无向连通图(无论如何(1)都会在一个连通块里),然后剩下的部分显然是随意选择。注意这里的关系是怎么推导的:确定特殊点(1)号节点的相关信息,然后写出卷积的关系式。这是一个很常用的方法,但是注意一点,并不是无论何时都是选择(1)号点所在的连通块,能够特殊选择是因为你选择了(1)号点和其他点是没有区别的,接下来就有不会选择(1)号点相关信息的题目。
接下来这题就可以接着往下推了,通常这种式子看见组合数直接拆,因为组合数拆完之后有(m!(n-m)!)的形式,往往可以往卷积的方向靠。
拆开后得到了(g(n)=sum_{i=1}^nfrac{(n-1)!}{(i-1)!(n-i)!}f(i)g(n-i))
这个式子我们对其每一项进行一定的分类,我们就可以写成一个很好看的形式(frac{g(n)}{(n-1)!}=sum_{i=1}^nfrac{f(i)}{(i-1)!}frac{g(n-i)}{(n-i)!})
这样子的形式很明显就是(EGF)了。
(F(x)=sum_{i=1}frac{f(i)}{(i-1)!}x^i,G1(x)=sum_{i=1}frac{g(i)}{(i-1)!}x^i,G2(x)=sum_{i=0}frac{g(i)}{i!}x^i)
那么上面的那个式子就可以写成(G1(x)=F(x)G2(x))
这样子再往下走就是(F(x)=frac{G2(x)}{G1(x)}),只需要多项式求逆就可以得出结果。

从这道题目中就可以很明显的感受到难点在哪里,并不是在于最终的生成函数,而是在于前面如何列出关系式来进行进一步的推导。这里经常可以搭配上多项式开方和求逆。

背包问题

显然背包的转移可以写成生成函数的形式。这里考虑的是背包的计数问题。转移显然是(f[i]=sum f[i-V_k])
比如说(01)背包,统计恰好放了体积为(V)的方案数,那么把(f)数组看成生成函数,那么我们可以把每个物品写成(1+x^{V_i})的形式,那么最终的答案就是所有物品多项式的成绩。考虑这里怎么乘,如果一个个乘的话复杂度是(O(n^2))的。然而这个东西是可以做到更加优秀的复杂度的。先看看更加极端的背包问题。
比如完全背包,物品个数不受限制,那么就是这道题目【洛谷4389】付公主的背包
显然每个重量的物品构成的生成函数是(sum_{i=0}^{infty}[i\\%V_k=0]x^i),化简一下可以写成(frac{1}{1-x^{V_k}})
考虑怎么求(prod frac{1}{1-x^{V_k}})。我们直接乘显然束手无策,然而我们可以把每一项分开求(ln),求和之后再(exp)回去就好了。
考虑如何求解这个东西的(ln)。因为(ln(frac{1}{x})=-ln(x)),所以只考虑求(ln(1-x^V))
推导如下((dx)这种东西懒得写了):
[egin{aligned} F(x)&=int F'(x)=int frac{-Vx^{V-1}}{1-x^V}&=int -Vx^{V-1}(sum_{i=0}^{infty}x^{Vi})&=-int sum_{i=0}^{infty}Vx^{V(i+1)-1}&=-sum_{i=1}^{infty}frac{x^{Vi}}{i} end{aligned}]
所以我们求解的(ln)化简之后就是(sum_{i=1}^{infty}frac{x^{Vi}}{i})
那么只需要开一个桶记录一下每一个重量的物品的个数,然后暴力枚举其重量的倍数,把对应的(1/i)的贡献加进去,最后多项式(exp)就好了。
(我上面那篇题解里面的写法是直接算(1-x^V)的贡献,最后再求逆,事实上并不需要那样子)
那么(01)背包或者多重背包之类的问题可以类似解决。

套路1

【洛谷4705】玩游戏
题意:求((a_x+b_y)^t)的期望。

对于给定的(t)而言,我们可以直接二项式定理展开。
[egin{aligned} sum(a+b)^k&=sumsum_{i=0}^kC_k^ia^ib^{k-i}&=sum_{i=0}^kC_k^i(sum a^i)(sum b^{k-i})&=sum_{i=0}^kfrac{k!}{i!(k-i)!}(sum a^i)(sum b^{k-i})\\ &=k!sum_{i=0}^kfrac{sum a^i}{i!}frac{sum b^{k-i}}{(k-i)!} end{aligned}]
所以实际上我们要求的东西就只有(sum a^i,sum b^i)。这两相同,只考虑其中一样。
考虑前面如何算的多项式的(ln)
我们换成((1+ax))再来计算。
[egin{aligned}ln(1+ax)&=int frac{a}{1+ax}=int asum_{i=0}^{infty}(-a)^ix^i\\&=sum_{i=1}^{infty}frac{(-1)^{i-1}}{i}a^ix^iend{aligned}]
构造(F(x)=prod(1+a_ix)),那么
[egin{aligned}ln(F(x))&=sum_{i=1}^nsum_{j=1}^{infty}frac{(-1)^{j-1}}{j}a_i^jx^j\\&=sum_{j=1}^{infty}frac{(-1)^{j-1}}{j}x^jsum_{i=1}^n a_i^jend{aligned}]
因此,我们只需要求出(ln(F(x))),再对于每一项还原一下系数就是我们要求的的东西了。
显然(F(x))可以分治+(FFT)求出来,直接求(ln)就做完了。
最后再回到题目要求的东西,只需要再做一次卷积就好了。

To be continue

以上是关于[复习]多项式和生成函数相关内容的主要内容,如果未能解决你的问题,请参考以下文章

快速傅立叶变换(FFT)相关内容汇总

「总结」多项式生成函数相关

生成函数入门

多项式&生成函数

python之路——15

生成函数