【学习笔记】数论之生成函数基础

前言

一直不是很会生成函数,但是平常遇到的数论题,很多地方都是会用到生成函数,现在正好有了时间可以搞一搞

未来说不定会补上 NTT。

FFT

(下文极有可能有一些加一减一的不合理的地方,可能以后会修修)

如果不会 FFT 那么生成函数肯定就完全做不了题了。(写过一篇不过当时根本不理解,胡写的太垃圾了)

考虑这样的一个问题:给定两个多项式 A(x)=a0+a1x+a2x2+...+an1xn1B(x)=b0+b1x+b2x2+...+bm1xm1,求他们的积。

如果只是暴力地做这个问题的话需要 O(nm) 的复杂度就爆炸
了。

但是我们知道,多项式除了系数表示还有点值表示,下面认为 n=m 因为显然可以用 0 补位。

不妨假设 A 的点值表示为 (x0,A(x0))(x1,A(x1))...(xn,A(xn))B 的点值表示为 (x0,B(x0))(x1,B(x1))...(xn,B(xn)),则设 C=A×B,则显然可得 C 的点值表示为 (x0,A(x0)B(x0))(x1,A(x1)B(x1))...(xn,A(xn)B(xn))

而我们知道 n 个不重的点可以唯一确定一个 n1 次多项式,而我们 C 可以理解为一个 n+m 次多项式,所以我们只需要找到 n+m+1 个点将 A,B 均转化为点值表示然后得到 C 的点值表示,再将点值表示转化为系数表示就好了。

下面问题就转化为了给定一个多项式 A(x)=a0+a1x+a2x2+...+an1xn1 如何求出它的点值表示。

点值表示关键就在于选择哪些点来表示,经过前人可以知道我们选择复数域上的 n 次单位根就满足十分优良的性质。

这里就要介绍一下复数域上的单位根是什么东西(不理解可以自己百度百科):

形如 a+bi 的数就被称为复数,其中 i=1

复数相加就相当于在复平面上两个向量相加,结果为它们形成的平行四边形的对角线。

复数相乘在复平面上相当于长度相乘,俯角相加。

而单位根就要引入单位圆的概念,单位圆就是与原点距离为 1 的点构成的集合,而 n 次单位根是指的将单位圆分成 n 份后得到的 n 个向量,第 i 个向量我们记为 wni,特别的 wn0=(1,0)
如下图所示为一个 8 次单位根:

根据图像我们可以得到单位根的一些性质,而且只要满足某些关键性质的数理论上都可以作为我们选择的点:

  1. ij,wniwnj
  2. wnk=cos(2πnk)+isin(2πnk)
  3. wnn=wn0=1
  4. w2n2k=wnk
  5. wnk+n2=wnk

上述性质的证明放在复平面上显然,就不多写了。

这个知识知道了我们就考虑开始化简给定的多项式,第一步就是按奇偶分类,设:

A1(x)=a0+a2x+a4x2+...+an2xn21A2(x)=a1+a3x+a5x2+...+an1xn21

A(x)=A1(x2)+xA2(x2),考虑将单位根带入,这里注意我们只需要枚举 k[0,n21] 因为另一半可以直接在这个基础上加 n2 得到。

A(wnk)=A1(wn2k)+wnkA2(wn2k)=A1(wn2k)+wnkA2(wn2k)A(wnk+n2)=A1(wn2k)wnkA2(wn2k)=A1(wn2k)wnkA2(wn2k)

发现这其实就是一个递归下去的子问题,递归树可知复杂度为 O(nlogn)

下面就是考虑给定一个多项式的点值表示怎么转化为系数表示,此时给定的点值表示一定为 (wni,A(wni)),为了下文方便我们直接令 A(wni)=yi,设:

Ck=i=0n1yi(wnk)i

可以发现的一点是 ai=Ci×n,所以只要我们可以快速求出 C 我们就可以得到 A 的系数表示。
yi 视为已知数,构造:

B(x)=y0+y1x+y2x2+...+yn1xn1

则有:

C(k)=B(wnk)

也就是说我们要求出 B 这个多项式在 n 次单位根的相反数位置的值,也就是和我们上述解决的问题多了一个负号,可以证明的是这两种方式可以套用同一个解决方法,完全等价。

现在其实就差了一个问题就是为什么 ai=Ci×n,下面就给出证明过程:

Ck=i=0n1(j=0n1aj(wni)j)(wnk)i=i=0n1(j=0n1aj(wnj)i)(wnk)i=j=0n1aji=0n1(wnjk)i

在内层求和中 jk 是不会改变的,所以考虑构造:

S(x)=1+x+...+xn1

则:

S(wnk)=1+wnk+...+(wnk)n1

此时分类讨论。

k0,则:

wnkS(wnk)=wnk+(wnk)2+...+(wnk)n=wnk+(wnk)2+...+1

所以:

(1wnk)S(wnk)=0

因为 k0 所以 1wnk0S(wnk)=0

k=0,则:

S(wnk)=n

所以只有当 jk=0j=k 时式子内部的和不为 0,也就是 Ck=ak×n

至此我们的 FFT 就完结撒花了(吗?)

我们会发现这是一个递归的版本实际运行效率极慢,所以要考虑换成迭代的版本。

因为我们每一次都会执行奇偶分类的操作所以我们也必须得到操作后的序列才能自底向上合并,手动模拟一下结果就如下图所示:

可以发现如果我们的 n 不为 2 的整数次幂那么这些操作就很难进行,所以就可以使用 0 补齐。将第 i 位经过操作后的位置记为 rev[i],那么在二进制下 rev[i] 就是 i 的翻转。

具体求解 rev[i] 可以考虑将二进制下最后一位拿出来,得到剩下的位翻转的结果,然后再将最后一位放到最前面。

代码:

点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 300010;
const double PI = acos(-1);

int n, m;
struct Complex
{
    double x, y;
    Complex operator+ (const Complex& t) const
    {
        return {x + t.x, y + t.y};
    }
    Complex operator- (const Complex& t) const
    {
        return {x - t.x, y - t.y};
    }
    Complex operator* (const Complex& t) const
    {
        return {x * t.x - y * t.y, x * t.y + y * t.x};
    }
}a[N], b[N];
int rev[N], bit, tot;

void fft(Complex a[], int inv)
{
    for (int i = 0; i < tot; i ++ )
        if (i < rev[i])
            swap(a[i], a[rev[i]]);
    for (int mid = 1; mid < tot; mid <<= 1)
    {
        auto w1 = Complex({cos(PI / mid), inv * sin(PI / mid)});
        for (int i = 0; i < tot; i += mid * 2)
        {
            auto wk = Complex({1, 0});
            for (int j = 0; j < mid; j ++, wk = wk * w1)
            {
                auto x = a[i + j], y = wk * a[i + j + mid];
                a[i + j] = x + y, a[i + j + mid] = x - y;
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i <= n; i ++ ) scanf("%lf", &a[i].x);
    for (int i = 0; i <= m; i ++ ) scanf("%lf", &b[i].x);
    while ((1 << bit) < n + m + 1) bit ++;
    tot = 1 << bit;
    for (int i = 0; i < tot; i ++ )
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    fft(a, 1), fft(b, 1);
    for (int i = 0; i < tot; i ++ ) a[i] = a[i] * b[i];
    fft(a, -1);
    for (int i = 0; i <= n + m; i ++ )
        printf("%d ", (int)(a[i].x / tot + 0.5));

    return 0;
}

这样我们通往生成函数的第一大关就算结束啦。

生成函数入门

本篇只讲解一些基础知识即入门所以很多地方注重理解不注重严谨,进阶的可能以后会有。

首先就是要知道什么是生成函数,以及它可以干什么。

生成函数名字很高级,其实本质它就是多项式,也就是给定一个任意序列 a0,a2,...,an,...,我们将其作为多项式的系数带入到多项式当中即变成 A(x)=a0+a1x+anxn+,在生成函数中我们默认 1<x<1,所以其实就是一个序列和多项式的映射。

我们为什么要有这种映射呢,毕竟映射不一定可以简化问题。因为我们对于多项式的研究十分深入,可以使用各种多项式的手段解决这个问题。

对于生成函数解决的一般问题就是求方案数的问题,这类问题一般我们都会采取乘法原理什么的来解决,可以发现的是乘法原理本质上就是多项式的乘法,所以就可以转化为多项式问题了。

下面以一个例题来感受一下这个过程是什么:

题目描述:
有三个砝码重量分别为 1g2g3g 每个砝码可以选或者不选,询问能够组成的重量有多少种。

我们考虑将 ai 定义为组成的重量为 i 的方案数,考虑对于每一个砝码求出其生成函数,直接将生成函数乘起来就是答案。设 fi 表示第 i 个砝码的生成函数则有:

f1(x)=1+xf2(x)=1+x2f3(x)=1+x3

f(x) 表示最终的答案,则:

f(x)=f1(x)f2(x)f3(x)=(1+x)(1+x2)(1+x3)=1+x+x2+2x3+x4+x5+x6

我们发现多项式乘法其实就是乘法原理,也就是我们看每个砝码究竟是选还是不选然后继续向下。

这样可能就对生成函数有点感觉了,下面再来一道题加深一下印象吧。

题目描述:
有三个砝码重量分别为 1g2g3g,重量为 1g 的砝码有两个,重量为 2g 的砝码有无限个,重量为 3g 的砝码有 1 个,每个砝码可以不选也可以选任意多个,询问组成重量为 4 的方案数有多少种。

依旧是上述的套路:

f1(x)=1+x+x2f2(x)=1+x2+x4+...f3(x)=1+x3

则:

f(x)=f1(x)f2(x)f3(x)

自己枚举一下就会发现,方案数总共有 3 种。

下面就是一道生成函数的入门典题了

题目描述:
m 个物品,每种物品有无限个,询问从中选择 k 个的方案数有多少种。

根据我们已知的组合数学常识,可以直接插板法解决,这个问题即求

x1+x2+...+xm=k(xi0)

的解的个数,但是插板法只可以解决 xi1 的情况,那么就强制每个多 1 就可以了,就转化为:

x1+x2+...+xm=k+m(xi1)

这个问题就是 k+m 个球放到 m 个盒子里面,也就是在 k+m 个球之间m1 个板的方案数,答案即:

(k+m1m1)

下面考虑怎么使用生成函数解决这个问题:

第一步显然是构造每一个物品的生成函数,此时的 ai 其实就是相当于选择 i 个的方案数:

fi(x)=1+x+x2+...=1xn1x=11x

上述过程因为 n1<x<1,所以 xn0 所以可以直接省略,其实上述也代表了我们生成函数里的一个重要等式:

1+xp+x2p+...=11xp

那么我们答案的生成函数就是:

f(x)=i=1mfi(x)=1(1x)m

这个东西我们是不会展开的,但是我们根据组合意义推到了这个东西的展开形式:

f(x)=1(1x)m=(0+m1m1)+(1+m1m1)x+(2+m1m1)x2+...

也就是我们得到了这样一个重要的展开:

1(1x)m=i=0(i+m1m1)xi

这个形式比较丑陋,换个形式就好看多了:

1(1x)m+1=i=0(i+mi)xi

最后再看一道生成函数的真实题目,我们的生成函数就算是成功入门了。

题目描述:
给定 8 个物品,每个物品选择的限制如下,询问恰好选择 n 个的方案数并对 10007 取模,1n10500
1.第一个物品必须选择偶数个
2.第二个物品选择 0 个或 1
3.第三个物品选择 0 个、1 个或 2
4.第四个物品选择奇数个
5.第五个物品选择 4 的倍数个
6.第六个物品选择 0 个、1 个、2 个或 3
7.第七个物品选择 0 个或 1
8.第八个物品选择 3 的倍数个

第一步就是将每个物品对应的生成函数求出来:

f1(x)=1+x2+x4+...=11x2f2(x)=1+x=1x21xf3(x)=1+x+x2=1x31xf4(x)=x+x3+x5+...=x1x2f5(x)=1+x4+x8+...=11x4f6(x)=1+x+x2+x3=1x41xf7(x)=1+x=1x21xf8(x)=1+x3+x6+...=11x3

下面就是愉快的消消乐环节了:

f(x)=f1(x)f2(x)f3(x)f4(x)f5(x)f6(x)f7(x)f8(x)=x(1x)4=x(i=0(i+33)xi)=i=0(i+33)xi+1

答案就是 xn 的系数,即:

(n+23)=(n+2)(n+1)n6

生成函数入门完结撒花,下面就是一些更为深入和具体的东西啦。

OGF

基础知识

OGF 全称为 oridinary generating function 也就是普通生成函数,这个东西的定义与我们上文的生成函数入门里讲的是一致的。

对于一个序列 a1,a2,...,an,...,它的 OGF 即 f(x)=a0+a1x+a2x2+...+anxn+...

对于生成函数我们常用的方法就是构造封闭形式,也就是上文中提到的那些看上去特别简洁的式子,这里给出几个常见的封闭形式,下文就围绕这几个封闭形式展开:

f(x)=1+x+x2+x3+...=11xf(x)=1+ax+a2x2+a3x3+...=11axf(x)=i=0xik=11xkf(x)=i=0cixik=11cxkf(x)=(n0)x0(n1)x1+(n2)x2(n3)x3+...+(nn)xn=(1x)nf(x)=(n0)x0+(n+11)x1+(n+22)x2+...=1(1x)n+1

第五个式子就是二项式定理展开,其他的都在上述入门的讲解里面推导过。
除了这些还有两个不会证明的形式:

f(x)=0+x+12x2+13x3+...=ln11x=ln(1x)f(x)=1+x+12!x2+13!x3+...=ex

OGF 的乘法就比较有意思的(下文中 A[i] 代表第 i 项的系数):

C=AB,则有:C[x]=i+j=xA[i]B[j],所以其实这个东西就是加法卷积或者叫做计数背包。

所以其实此时重新回顾一下我们入门中讲解的知识,其实就是因为 OGF 的乘法可以理解为加法卷积,所以我们可以单独构造然后乘起来就是答案。

基础知识大概就这些了,下面就是一些 OGF 的应用啦。

斐波那契数列

第一步当然就是推导大名鼎鼎的斐波那契数列的通项公式了。

斐波那契数列的定义为 a0=0,a1=1,ai=ai1+ai2(i2),设它的 OGF 为 f(x),则根据定义显然:

f(x)=a0+a1x+a2x2+...

考虑想一些办法让 f(x) 表示自己,也就是错位一下看看:

f(x)=a0+a1x+a2x2+a3x3+...xf(x)=a0x+a1x2+a2x3+...x2f(x)=a0x2+a1x3+...

可以显然发现当次数大于等于 2xf(x) 的系数和 x2f(x) 的系数和为 f(x) 的系数,次数小于等于 1 的项可以手动修正:

f(x)xf(x)x2f(x)=x

解得:

f(x)=x1xx2

感觉这一步比较神仙,就是因为我们已知的东西根本推不出来这个,就想办法把这个转化到已知的上面即我们认为下面这个方程一定有解:

A1ax+B1bx=x1xx2

通分:

(AAbx+BaBx)(1ax)(1bx)=x1xx2

系数对应相等:

{A+B=0AbaB=1a+b=1ab=1

解的:

{A=15B=15a=1+52b=152

带入后把封闭形式展开就得到了斐波那契数列通项公式:

x1xx2=i=0xi15[(1+52)i(152)i]

卡特兰数

卡特兰数的应用十分广泛,例如:

  • 长度为 n 的仅包含小括号的合法括号序列数
  • 节点个数为 n 的二叉树个数
  • n×n 的网格图上,不跨越(并非不可触碰)对角线地从 (1,1) 走到 (n,n) 地方案数。

其部分序列为:

H0 H1 H2 H3 H4
1 1 2 5 14

卡特兰数显然满足如下递推式:

Hn=i=0n1HiHni1(n2)

其中 H0=1,H1=1
如果以括号序列来理解就是枚举最后块内部选择多少个。

下面就是想办法求解这个序列对应的 OGF 的封闭形式,在求之前要先引入一个新的定理,广义二项式定理:

重新定义组合数运算为:

(nm)=nm_m!(nC,mR)

注意的是此时 n 在复数域上,那么对于 aC 都有:

(1+x)a=i=0(ai)xi

对于卡特兰数的递推式观察会发现很像卷积,所以考虑能不能用卷积构造:

f(x)=i=0Hixi=1+i=1j=0i1HjxjHnj1xnj1x=1+xi=0Hixij=0Hjxj=1+xf2(x)

这就是一个一元二次方程,解得:

f(x)=1±14x2x

则显然应取:

f(x)=114x2x

根据广义二项式定理得:

(14x)12=i=0(12i)(4x)i=i=0(12)i_i!(4x)i

考虑化简 (12)n_

(12)n_=12×12×32××(2n3)2=(1)n1(2n3)!!2n=(1)n1(2n2)!2n(2n2)!!=(1)n1(2n2)!22n1(n1)!

上述推导涉及两个双阶乘的化简技巧:

n!!=(n1)!(n1)!!(2n)!!=2nn!

考虑把我们得到的式子带入原式:

(14x)12=1+i=1(1)i1(2i2)!i!(i1)!22i1(4x)i=1i=1(2i2)!i!(i1)!2xi=1i=1(2i1i)12i12xi

继续把这个式子带回去:

f(x)=114x2x=12xi=1(2i1i)12i12xi=i=1(2i1i)12i1xi1=i=0(2i+1i+1)12i+1xi=i=0(2ii)1i+1xi

也就是成功得到了通项公式,即:

Hn=(2nn)1n+1

OGF 的基础部分就先告一段落了,有没有进阶部分就以后再说吧。

多项式全家桶

下文默认 F,G 代表多项式,F(i) 代表将 i 带入多项式的值,F[i] 代表多项式 xi 的系数,F(x) 代表多项式 F

多项式加减:

F(x)+G(x)=(F[i]+G[i])xiF(x)G(x)=(F[i]G[i])xi

多项式乘法(加法卷积):

F(x)G(x)=ijF[j]G[ij]xi

F(x)G(x)=1 则称 F(x)G(x) 互为乘法逆元。

多项式 ln,exp

定义:

ln(F(x))=i=1(1F(x))iiexp(F(x))=eF(x)=i=0F(x)ii!

求多项式 ln
题目描述:给定 A(x),求 B(x)=ln(A(x))
两边求导后积分得:

B(x)=(A(x)A(x))

EGF

基础知识

EGF 全称为 exponential generating function,即指数生成函数。

若给定序列 a0,a1,a2,...,an,...,则其指数生成函数为:F(x)=i=0aixii!

EGF 的乘法十分常用,因为可以得到组合数:

(FG)[k]=i+j=k(ki)F[i]G[j]

推导也是简单的,也就是不妨先将 EGF 当作 OGF 处理:

(FG)[k]k!=i+j=kF[i]i!G[j]j!

移项:

(FG)[k]=i+j=kk!i!j!F[i]G[j]=i+j=k(ki)F[i]G[j]

下面列举几个常见的封闭形式(这里面列举的就是 a0,a1,a2,a3,...):

{1,1,1,1,...}=ex{1,1,1,1,...}=ex{1,0,1,0,...}=ex+ex2{1,c,c2,c3,...}=ecx{1,a1_,a2_,a3_,...}=(1+x)a

1,2,4 就是根据多项式 exp 定义推的,第 3 就是第 1 和第 2 相加除二,第 5 就是根据组合意义(二项式定理)。

下面就用 EGF 推几个简单题,感受一下 EGF。

题目描述:
有红绿蓝三种颜色,要用这三种颜色染长度为 n 的序列,要求红蓝两种颜色必须染偶数次,绿色没有限制,询问染色的方案数。

考虑如果我们知道了染某两种颜色的生成函数,那么合并的时候是一个什么形式:i+j=n(ni)F[i]G[j],不能单纯地分配个数,因为在给定个数下怎么选也会造成方案的不同。

所以这其实就是对应的 EGF 卷积,那么找到它们的 EGF 就可以得到答案:

显然:

F绿=i=0xii!=exF=F=i=0x2i(2i)!=ex+ex2

相乘就可以得到答案:

ex(ex+ex2)2=e3x+2ex+ex4

这个东西就可以对应着我们的封闭形式,就分别展开然后求和就好了:

i=03i+2+(1)i4×xii!

对应的系数就是答案了。

题目描述:
n 种颜色,第 i 种颜色可以染 ai 次,每个位置不可被重复染色,问最后染色次数为 m 的方案数。
1n,m10

分析同上,所以这也可以表示为 EGF 卷积的形式。

对于第 i 种颜色,其对应的 EGF 就是:i=0aixii!

直接根据定义随便乘就好了。

题目描述:
n 个点有标号且最大点度数为 m 的无根树个数。
题目来源:P5219 无聊的水题 I

看到这个显然想到使用 prufer 序列转化,也就是可以转化为:长度为 n2 的序列,每个位置可以填 [1,n],要求出现最多的点的出现次数恰好为 m1 的方案数。

恰好不好搞,差分一下变为求 m1 的方案数减去 m2 的方案数。

现在假设我们在求 t 的方案数,可以很好地发现这就是对应 EGF 卷积的形式,因为其实还是在染色。

对于每一种数,其对应 EGF 显然都是相同的,即:

F(x)=i=0txii!

注意上面这个式子的 i 有上界,所以不能很好地转化为封闭形式,而我们的答案显然为:

[xn2]F(x)n

就是让我们求多项式快速幂的 xn2 的系数,套板子就好了。

EGF 中多项式 exp 的组合意义

通过一个实例来引入:
考虑长度为 n 的排列数的 EGF 为:

F(x)=i=0i!xii!=11x

长度为 n 的圆排列方案数为 (n1)!,所以其 EGF 为:

G(x)=i=1(i1)!xii!=i=1xii=ln(11x)

可以发现:exp(G(x))=F(x)

结合这两个实例的内在联系,不难猜测 exp 存在某些惊人的组合意义。

以上述为例,我们可以将排列划分为 k 个集合,每个集合内都是一个置换环,也可以视为圆排列,我们总体的方案数就是每个圆排列的方案数的乘积。

形象一点就是:

n 个有标号元素,求满足条件 A 的方案数,设其生成函数为 f(x)

且上述方案数等于将 n 个有标号元素划分为若干个集合,每个集合满足条件 B 的方案数的乘积。设满足条件 B 的生成函数为 G(x)

则有 exp(G(x))=F(x)

这个结论本质上就是在说,如果我们能将集合划分为若干其他元素,并且我们知道每个元素的信息,那么直接 exp 就可以得到我们需要的信息。

此结论对于无标号的 OGF 和有标号的 EGF 均成立。

下面通过几个简单的例子,来更加深刻地理解上面的这个结论。

  • n 个点带标号的生成树个数的 EGF 为 G(x),则 n 个点带标号的森林个数为 exp(G(x))
  • n 个点带标号无向连通图个数的 EGF 为 G(x),则 n 个点带标号无向图个数为 exp(G(x))

理解了上述的结论,下面就来看几个应用吧:

题目描述:
长度为 n 的错排定义为不存在 pi=i,求错排方案的生成函数 F(x)

我们可以将错排也划分为若干个置换环,不过不能存在长度为 1 的置换环,设置换环对应的 EGF 为(也就是圆排列):G(x)=i2xii!=ln(11x)x

那么就有 F(x)=exp(G(x))

题目描述:
n 个点带标号无向连通图的个数。
题目来源:[集训队作业2013] 城市规划

考虑一个正常的分析过程:

无向连通图会是什么的构成元素呢,其实就是无向图,因为无向图一定可以划分成若干个无向连通图。

设连通图的 EGF 为 G(x),一般图的 EGF 为 F(x) 则:exp(G(x))=F(x),而我们要求 G(x),也就是直接两边取 ln 得到:G(x)=ln(F(x))

那么 F(x) 是好求的,因为其实就是可以理解为每一条边选择或者不选择即:

F(x)=i=02(i2)xi

posted @   linyihdfj  阅读(128)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示