生成函数浅讲
感觉这是一个非常牛逼的东西,写了点自己的感悟,可能讲得不是很清楚。
生成函数的定义就比较牛,将数列 {ai} 写成一个函数 A(x)=∑aixi 的形式叫做普通生成函数。
此处的 xi 没有实际意义,只是一个占位符。对于生成函数来说,绝大数多项式的运算法则都是可以用的。
比如:
A(x)+B(x)=∑(ai+bi)xi
A(x)B(x)=∑i∑j(aibj)xi+j。
一些常见的数列都可以写作生成函数的形式,而且往往都有较为简单的封闭形式:
{1,0,0…}→1
{0,1,0…}→x
{1,1,1…}→1+x+x2+⋯=11−x
{1,0,1,0,1…}→1+x2+x4⋯=11−x2
我们用 [xn] 表示 xn 的系数,即数列的第 n 位。
拓展一下它们的乘积有什么含义
乘 xk 相当于将数列向后平移 k 位。
乘 11−x 相当于对数列做一次前缀和,而 1(1−x)k 就是求 k 次前缀和,把这个过程写出来就会发现很像一个旋转了一下的杨辉三角,第 i 就是 (i+k−1k−1) 。
乘 11−xp 后第 i 位就是原数列第 i 位往前第 p 的整数倍的位的前缀和(听着很绕其实很简单,笔者试图用简洁的语言的描述但语文水平不允许),类似的 1(1−xp)k 的数列只在 p 的整数倍的位上有值并且第 ip 位为 (i+k−1k−1) 。
然后我们就可以用生成函数做一些很牛的事情。
先看一道简单题:
题意:在许多不同种类的食物中选出 n 个,每种食物的限制如下:
-
承德汉堡:偶数个
-
可乐:0 个或 1 个
-
鸡腿:0 个,1 个或 2 个
-
蜜桃多:奇数个
-
鸡块:4 的倍数个
-
包子:0 个,1 个,2 个或 3 个
-
土豆片炒肉:不超过一个。
-
面包:3 的倍数个
每种食物都是以「个」为单位,只要总数加起来是 n 就算一种方案。对于给出的 n ,你需要计算出方案数,对 10007 取模。(n≤10500)
sol:首先对于每种食物构造一个数列,数列的第 i 位表示总数为 i 时这种食物有多少选法,显然只有 0/1 ,如下:
{1,0,1,0,1…}→1+x2+x4⋯=11−x2
{1,1,0,0,0…}→1+x
{1,1,1,0,0…}→1+x+x2
{0,1,0,1,0…}→x+x3+x5⋯=x1−x2
{1,0,0,0,1,0…}→1+x4+x8⋯=11−x4
{1,1,1,1,0…}→1+x+x2+x3
{1,1,0,0,0…}→1+x
{1,0,0,1,0…}→1+x3+x6⋯=11−x3
然后把它们乘起来就把数量上的加法转化为了系数上的相加,所以总数为 n 的答案就变成了 xn 的系数。结果化简一下就是 x(1−x)4 ,然后结合上面总结的 x(1−x)k 的规律,可以得到答案就是 (n+23),边读入边取模即可。
这道题中的生产函数是比较显然的,但是很多题目需要我们通过生产函数转化一些其他做法,并且最终生成函数的通项的求法也会有很多种,下面看一些不同解法的题。
定义 F0=0,F1=1,Fn=Fn−1+Fn−2(n>1) (其实就是斐波那契数列)
∑m∏i=1Fai
m>0
a1,a2...am>0
a1+a2+...+am=n
由于答案可能非常大,所以要对 109+7 取模。(1≤n≤1010000)
sol :首先考虑DP,设 fi,j 表示固定 m 为 i ,n 为 j 时的答案。然后就会发现一个非常显然的暴力转移:
fi,j=j∑k=0fi−1,k×Fj−k
按理来说这里的 k 只能从 1 枚举到 j−1 但是 F0=0,fi,0=0 所以对答案是没有影响的。最终答案就应该是 n∑i=1fi,n 。
我们把 fi−1 到 fi 的递推看成一次卷积操作,就会发现 fi=Fi ,那么答案就是:
[xn]n∑i=1Fi
看着似乎不太可做,但是我们发现当 i>n 时,Fi 的第 n 项显然为 0 所以可以改写成:
∞∑i=1Fi=[xn]F1−F
把斐波那契数列的生成函数 F=x1−x−x2 代进去就是 x1−2x−x2 ,这个生成函数可以用特征方程来解出来,这里详细讲一下。
分子就是平移一位是好处理的,而分母转换一下就是:
f0=1,f1=2,fn+1=2fn+fn−1
我们考虑把后面的式子写成一种等比数列的形式:
fn+1−pfn=q(fn−pfn−1)
然后我们可以求出这个等比数列的通项:
fn+1−pfn=qn(f1−pf0)fn+1−pfn=qn(2−p)
p 和 q 应该满足条件:
pq=−1,p+q=2
所以 p 和 q 是方程 x2−2x−1=0 的两个根 1+√2 和 1−√2 ,
同时这里也可以看出 p 和 q 是等价的(可以交换),再结合上面的式子可以得到一个方程组:
fn+1−qfn=pn(2−q)fn+1−pfn=qn(2−p)
然后我们把 fn 解出来,再把 p 和 q 代进去:
fn=(1+√2)n+1−(1−√2)n+12√2
因为这是平移过的,所以最后答案是
fn−1=(1+√2)n−(1−√2)n2√2
题意:对于一个十元组 (a1,a2,b1,b2,c1,c2,d1,d2,e1,e2),定义它合法当且仅当满足下列条件:
-
0≤a1<a2。
-
0≤b1<b2。
-
0≤c1<c2。
-
0≤d1<d2。
-
0≤e1<e2。
-
a1+a2+b1+b2+c1+c2+d1,d2+e1+e2≤n。
现在有 T 组数据,每次对于给定的 n 求所有合法十元组 (a2−a1)(b2−b1)(c2−c1)(d2−d1)(e2−e1) 之和对 109+7 取模的结果。
1≤T≤100,1≤n≤109。
这道题似乎有比较简单的组合意义解法,但是我觉得用生成函数做这道题更帅一些,其实是不会组合意义做法。之前写过题解,这里就不讲了。
题意:有一些卡牌,每张卡牌上有一个数字,具体的,有 bi 张卡牌上的数字为 ai。
求出拿走其中 m 张卡牌的贡献之和。贡献为这些卡牌的乘积。对于本质相同的卡牌组合,只算一次。
- n≤16,m≤1018,bi≤1017,1≤ai<mod
sol :
首先对于每种卡牌构造生成函数:
bi∑j=0ajixj=1−abi+1ixbi+11−aix
最终答案的就是:
n∏i=11−abi+1ixbi+11−aix=[xm]n∏i=11−abi+1ixbi+1n∏i=11−aix
这里因为 n 很小,只有 16 所以可以2n暴力把分子部分展开。假设展开有 w 项,第 i 项为 cixdi ,则累加每一项的贡献得到的答案就是:
w∑i=1ci×[xn−di]1n∏i=11−aix
现在问题变成了 2n 次询问分母某一位的值。处理分母可以考虑待定系数,如果可以写成求和的形式就好做多了,于是我们令:
n∏i=111−aix=n∑i=1pi1−aixn∑i=1pi1−aixn∏j=11−ajx=1n∑i=1pin∏j=1,j≠i1−ajx=1
一般来说生成函数中的 x 是没有实际意义的,但是这里我们可以考虑把它当作一个普通的函数,而普通函数有一个优势,可以代值求待定系数。所以我们代入 x=1ak 那么当 j=k 时,式子中 1−ajx=0 ,也就是说只有 i=k 这一项被保留了下来:
pin∏j=1,j≠i1−ajai=1pi=1n∏j=1,j≠i1−ajai
于是我们可以求出每一项的待定系数,现在再看一下分母的形式就非常好求了:
n∑i=1pi1−aix=n∑i=1[xk]pi1−aix=n∑i=1pi×aki
至此,我们得到了一个单次 O(n) 查询某一位的做法,最终复杂度:O(n2n)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】