Part 1 普通型生成函数 OGF(Ordinary Generating Function)
普通型生成函数的特征函数是 fi(x)=xi。
1.0 形式幂级数形式
回到生成函数的定义:
F(x)=i=0∑∞aifi(x)
OGF 的特征函数是 fi(x)=xi,也就是说,OGF 总是可以写成这样的形式:
F(x)=i=0∑∞aixi
由于 x 是无关紧要的,只是一个形式,并且这种写法带次幂,所以它被叫做形式幂级数形式。
注意到这种形式可以很方便地将生成函数还原回序列,所以从生成函数还原到序列之前往往要先化为形式幂级数形式。
1.1 OGF 的一些运算的意义
为了方便运算,所以生成函数往往规定原序列的负数项为 0。
假设有两个序列 a,b 和它们的 OGF F(x),G(x),那么:
-
相加
H(x)=F(x)+G(x)=i=0∑∞(ai+bi)xi
相当于是 a 和 b 对应项相加的序列 ci=ai+bi 的 OGF;
-
相减
H(x)=F(x)−G(x)=i=0∑∞(ai−bi)xi
相当于是 a 和 b 对应项相减的序列 ci=ai−bi 的 OGF;
-
乘 x
xF(x)=i=0∑∞aixi+1=i=0∑∞ai−1xi
相当于把原序列整体右移一位;
-
相乘
H(x)=F(x)G(x)=(i=0∑∞aixi)(i=0∑∞bixi)=i=0∑∞xij=0∑iajbi−j
相当于是 ci=j=0∑iajbi−j 的 OGF。组合意义如下:
ci 表示把 i 个无标号小球放入 A 和 B 两个有标号无序集合中,其中 A 集合中放 i 个球的方案数是 ai,B 集合中放 i 个球的方案数是 bi。
1.2 封闭形式
a={1,1,…} 这个数列的 OGF 的形式幂级数形式是 F(x)=i=0∑∞xi,考虑用更简洁的方法表示 F(x):
令 Gn(x)=i=0∑nxiGn+1(x)−Gn(x)=xn+1=1+xGn(x)−Gn(x)=1+(x−1)Gn(x)所以1+(x−1)Gn(x)=xn+1Gn(x)=x−1xn+1−1代入 n=∞,−1<x<1F(x)=G∞(x)=x−1−1=1−x1
也就是说,当 −1<x<1 时,F(x)=1−x1,这种**“最简形式”被定义为生成函数的封闭形式**。由于我们并不关心 x 的取值,所以可以认为 F(x)=x−11。注意到封闭形式有利于对生成函数进行各种运算,所以往往要把生成函数化为封闭形式再进行各种推导。
总结一下,利用生成函数来对数列进行各种推导主要分成以下几步:
- 确定特征函数,写出数列对应的生成函数的形式幂级数形式;
- 根据生成函数的形式幂级数形式求出它的封闭形式;
- 对所有要参加推导的数列都重复前两步以确定它们的生成函数的封闭形式;
- 根据题目的需要,对这些封闭形式进行各种运算;
- 将运算的结果还原回形式幂级数形式,获得答案序列;
1.3 一些常见数列的 OGF 的封闭形式
a={y,yp,yp2,yp3,yp4,…}a={0,1,1,1,1,…}a={0,1,0,1,0,…}a={1,2,3,4,5,…}ai=(in)n 是给定的常数ai=(nn+i)n 是给定的常数ai=ai−1+ai−2,a0=1,a1=1并推导出通项公式ai=j=0∑i−1ajai−j−1,a0=1
第一个:
F(x)=yi=0∑∞pixi=y(1+xpF(x))=1−xpy
第二个:
F(x)=i=1∑∞xi=xi=0∑∞xi=1−xx
第三个:
F(x)=i=0∑∞[i=2j+1]xi=i=0∑∞x2i+1=xi=0∑∞(x2)i=1−x2x
第四个:
F(x)=i=0∑∞(i+1)xi=i=0∑∞ixi+i=0∑∞xi=xF(x)+1−x1(1−x)F(x)=1−x1F(x)=(1−x)21
第五个:
F(x)=i=0∑n(in)xi=(1+x)i
第六个:
考虑组合意义,(nn+i) 相当于把 n+i+1 个相同的小球放进 n+1 个不同的盒子里的方案数,相当于 x1+x2+⋯+xn+1=i 的不同非负整数解的个数,相当于 n+1 个 F(x)=i=0∑∞xi 乘起来即 (i=0∑∞xi)n+1,写成封闭形式即为 (1−x1)n+1=(1−x)n+11。
所以 F(x)=(1−x)n+11。
第七个:
ai 就是斐波那契数列的第 i 项。
显然有 F(x)=xF(x)+x2F(x)−xa0+a0+xa1=1+xF(x)+x2F(x),解得 F(x)=1−x−x21。
进一步的,我们可以推导出 ai 的通项公式:
F(x)=1−x−x21=i=0∑∞(x+x2)i=i=0∑∞j=0∑i(ji)xi+j=i=0∑∞xij=0∑i(ji−j)
即 ai=j=0∑i(ji−j)。
但是这个式子是 O(n) 的,并不是我们熟悉的带 5 的神秘 O(1) 通项。
下面将用另一种方式推出 O(1) 的通项,考虑一类我们熟悉的 OGF —— yF(x)=i=0∑∞piyxi=1−xpy,由于斐波那契数列的生成函数的封闭形式是 1−x−x21,所以需要至少两个这样的 OGF 加起来通分之后才能出现最高次项,那么设:
1−x−x21=1−axA+1−bxB=(1−ax)(1−bx)A(1−bx)+B(1−ax)=1−(a+b)x+abx2A+B−(aB+Ab)x
所以有:
⎩⎪⎪⎪⎨⎪⎪⎪⎧aB+Ab=0A+B=1a+b=1ab=−1
解得:
⎩⎪⎪⎪⎨⎪⎪⎪⎧a=21−5b=21+5A=21−105B=21+105
所以有
F(x)=i=0∑∞((21−105)(21−5)i+(21+105)(21+5)i)xi=i=0∑∞(2(1−55)(21−5)i+(1+55)(21+5)i)xi
第八个:
ai 就是卡特兰数的第 i 项。
发现这个递推式很像卷积,所以考虑用卷积构造它的生成函数。
设 F(x)=i=0∑∞aixi,则有:
F(x)=1+i=1∑∞xij=0∑i−1ajai−j−1=1+xi=0∑∞xij=0∑iajai−j=1+xF2(x)=2x1±1+4x
现在的问题是取哪个根,我们将其分子有理化:
F(x)=1±1+4x2
代入 x=0,这样 F(x)=a0=1,显然若分母应取 1+1+4x=2,所以 F 的封闭形式为 F(x)=2x1−1+4x。
1.4 应用
在许多不同种类的食物中选出 n 个,每种食物的限制如下:
- 承德汉堡:偶数个
- 可乐:0 个或 1 个
- 鸡腿:0 个,1 个或 2 个
- 蜜桃多:奇数个
- 鸡块:4 的倍数个
- 包子:0 个,1 个,2 个或 3 个
- 土豆片炒肉:不超过一个。
- 面包:3 的倍数个
每种食物都是以“个”为单位,只要总数加起来是 n 就算一种方案。对于给出的 n 你需要计算出方案数,对 10007(质数)取模。
考虑对每种食物构造多项式,由于两种食物选出 n 个的方案数的生成函数就是它们的生成函数的卷积,所以多种食物总共选出 n 个的方案数的生成函数就是他们的生成函数全部卷到一起的结果。
接下来问题就变为求出每种食物的生成函数的封闭形式然后乘起来,最后还原回形式幂级数形式,得到答案。
这里给出每种食物的生成函数的封闭形式:
- 1−x21
- 1+x
- 1+x+x2=1−x1−x3
- 1−x2x
- 1−x41
- 1+x+x2+x3=1−x1−x4
- 1+x
- 1−x31
接下来要把它们乘起来:
(1−x2)(1−x)(1−x2)(1−x4)(1−x)(1−x3)(1+x)(1−x3)x(1−x4)(1+x)=(1−x2)(1−x)(1−x2)(1−x)(1+x)x(1+x)=(1−x)(1+x)(1−x)(1−x)(1+x)(1−x)(1+x)x(1+x)=(1−x)(1−x)(1−x)(1−x)x=(1−x)4x
考虑到 (1−x)4x=x(1−x1)4,相当于四个 {1,1,1,1,1,…} 的生成函数乘起来再让系数整体右移一位,也就是 xi=0∑∞(3i+3)xi=i=0∑∞(3i+2)xi,那么答案即为 (3n+2)。
1.4.1 [P6078 [CEOI2004] Sweets](https://www.luogu.com.cn/problem/P6078)
第 i 种糖果的生成函数显然是 1−x1−xmi+1,答案的生成函数就是 (1−x)ni=1∏n1−xmi+1。
考虑暴力展开,显然由于 ai=(nn+i) 的生成函数的封闭形式就是 (1−x)n+11,所以答案的生成函数可以化为:
(i=1∏n1−xmi+1)i=0∑∞(in+i−1)xi
前面的部分可以 O(2n) 暴力求出 x 的所有指数对应的系数 yixki,那么对于所有二元组 (yi,ki),它对答案的贡献即为:
yij=a−i∑b−ki(jn+j−1)=yi(j=0∑b−ki(jn−1+j)−j=0∑a−ki−1(jn−1+j))=yi((b−kin−1+b−ki+1)−(a−ki−1n−1+a−ki−1+1))=yi((b−kin+b−ki)−(a−ki−1n+a−ki−1))=yi((nn+b−ki)−(nn+a−ki−1))
注意到组合数可以直接暴力 O(n) 计算((mn)=m!(n−m)!n!,由于 m! 很小,所以可以暴力计算 n×(n−1)×(n−2)×⋯×(n−m+1)mod2004m! 然后除以 m! 再模 2004),时间复杂度即为 O(n2n)。
代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
const int S=15,p=2004;
int n,a,b;
int m[S];
inline int C(int n,int m)
{
if(n<0||m<0||m>n) return 0;
long long mod=p;
long long r1=1,r2=1;
for(int i=1;i<=m;i++) r1*=i,mod*=i;
for(int i=n;i>=n-m+1;i--) r2=r2*i%mod;
// printf("C(%d %d)=%d\n",n,m,r2/r1);
return r2/r1%p;
}
int dfs(int i,int y,int k)
{
if(i==n+1) return (y*(C(n+b-k,n)-C(n+a-k-1,n))+p)%p;
return (dfs(i+1,y,k)+dfs(i+1,-y,k+m[i]+1))%p;
}
int main()
{
scanf("%d%d%d",&n,&a,&b);
for(int i=1;i<=n;i++) scanf("%d",&m[i]);
printf("%d\n",dfs(1,1,0));
return 0;
}