跳转至

生成函数和多项式

生成函数就是把一个序列写进一个多项式的系数,然后通过研究这个多项式来研究这个序列。序列可以是有限的,也可以是无限的。由于多项式乘法对应着系数的卷积,因此生成函数可以处理很多和卷积有关的问题。

定义

给定序列 \(\{f_{n}\}\),定义多项式 \(F(x)\)

\[ F(x)=\sum_{i=0}f_ix^i \]

为它的生成函数。生成函数之间的加法、减法、乘法就是通用的定义(对位相加,对位相减,系数卷积)。对于除法,定义多项式 \(F(x)\) 的乘法逆元 \(G(x)\) 为满足 \(F(x)G(x)=1\) 的多项式。若生成函数的常数项不为 \(0\),那么它存在唯一的乘法逆元:

\[ \begin{cases} b_0=\frac{1}{a_0}\\ b_n=-\frac{1}{a_0}\sum_{i=0}^{n-1}a_{n-i}b_i \end{cases} \]

封闭形式

把生成函数写成级数的形式有时不容易研究。这时可以把它写成封闭形式。

封闭形式是指通过有限次的初等运算(加、减、乘、除、乘方)和初等函数(指数函数、对数函数、三角函数、反三角函数)组合而成的数学表达式。

考虑一个例子,对于生成函数

\[ F(x)=1+x+x^2+\cdots=\sum_{i=0}^{+\infty}x^i \]

考虑到 \(xF(x)+1=F(x)\),通过移项可以得到:

\[ F(x)=\frac{1}{1-x} \]

这样的东西叫做封闭形式。

更多封闭形式

等比数列 \(1,p,p^2,p^3,\cdots\) 的封闭形式为 \(F(x)=\frac{1}{1-px}\); 斐波那契数列 \(0,1,1,2,3,5,8,13,\cdots\) 的封闭形式为 \(F(x)=\frac{x}{1-x-x^2}\)

P4451 [国家集训队] 整数的lqp拆分

给定正整数 \(m\),求

\[ \sum_{\{a_i\}_{i=1}^n}\Bigl[\sum_{i=1}^na_i=m\Big]\prod_{i=1}^{n}fib(a_i) \]

第一个 \(\sum\) 表示枚举所有长度任意,取值为正整数的序列;\(fib(n)\) 表示斐波那契数列的第 \(n\) 项。

\(m\le 10^{1000}\),答案对 \(10^9+7\) 取模的结果。

设斐波那契数列的生成函数为 \(F(x)\),注意到答案的生成函数 \(G(x)=\sum_{i=1}F^i(x)\)\(G(x)=\frac{F(x)}{1-F(x)}\)。由 \(F(x)=\frac{x}{1-x-x^2}\)

\[ G(x)=\frac{x}{1-2x-x^2} \]

于是得到 \(\{g_n\}\) 的递推式和初值:

\[ \begin{cases} g_0=0\\ g_1=1\\ g_n=2g_{n-1}+g_{n-2} \end{cases} \]

解得特征根为 \(x_1=-1+\sqrt 2,~x_2=-1-\sqrt 2\)。带入初值解得通项公式为

\[ g_n=\frac{\sqrt 2}{4}\biggl(\Bigl(1+\sqrt 2\Big)^n-\Bigl(1-\sqrt 2\Big)^n\bigg) \]

打表得 \(\sqrt 2\equiv 59713600\pmod{10^9+7}\)(其中一个解)。

代码
#include<iostream>
#define int long long
using namespace std;
const int MOD = 1e9 + 7;
const int A = 1 + 59713600;
const int B = 1000000008 - 59713600;
const int C = 250000002l * 59713600 % MOD;

inline int qpow(int a, int b) {
    int res = 1;
    while(b) {
        if(b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

inline int qpow(int a, string &b) {
    int res = 1;
    for(int i = b.size() - 1; i >= 0; i--) {
        res = res * qpow(a, b[i] - '0') % MOD;
        a = qpow(a, 10);
    }
    return res;
}

string n;

signed main() {

    cin >> n;
    cout << C * (qpow(A, n) - qpow(B, n) + MOD) % MOD << endl;

    return 0;
}

P4841 [集训队作业2013] 城市规划

P4931 [MtOI2018] 情侣?给我烧了!(加强版)

\(n\) 对情侣来到电影院观看电影。在电影院,恰好留有 \(n\) 排座位,每排包含 \(2\) 个座位,共 \(2n\) 个座位。

现在,每个人将会随机坐在某一个位置上,且恰好将这 \(2n\) 个座位坐满。问恰好有 \(k\) 对情侣坐在同一排的方案数。

\(T\le 2\times 10^5,~n\le 5\times 10^6\)

说实话这题真不适合用生成函数,做法太垃圾了。但是既然原来在这,就把不用生成函数的做法也放在这吧。

发现只需求出 \(f_n\) 表示 \(n\) 对情侣有恰好 \(0\) 对和睦的方案数,那么询问的答案就是 \(\binom{n}{k}^2k!2^kf_{n-k}\)。考虑怎么求 \(f_n\),考虑递推,考查坐在第一排椅子上的两个人,分讨他们是两个男的,两个女的,还是一男一女,这四种情况的方案数应该是相等的。然后考查他们的两个配偶坐在哪里,然后继续分讨她们旁边的两个人是不是一对,如果是,就从 \(f_{n-3}\) 转移,如果不是,就从 \(f_{n-2}\) 转移。总之就是这样,系数是好推的,式子太长了看代码吧。

代码
#include<iostream>
#define int long long
using namespace std;
const int MOD = 998244353;
const int N = 5e6 + 10;

int T;
int n, k;

int f[N];
int inv[N], fact[N], ifact[N], pw2[N];

inline int C(int a, int b) { return fact[a] * ifact[a - b] % MOD * ifact[b] % MOD; }

signed main() {

    inv[1] = fact[0] = ifact[0] = pw2[0] = 1;
    for(int i = 2; i < N; i++) inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
    for(int i = 1; i < N; i++) fact[i] = fact[i - 1] * i % MOD;
    for(int i = 1; i < N; i++) ifact[i] = ifact[i - 1] * inv[i] % MOD;
    for(int i = 1; i < N; i++) pw2[i] = pw2[i - 1] * 2 % MOD;

    f[0] = 1, f[1] = 0;
    for(int i = 2; i < N; i++) {
        int tmp = i * (i - 1) % MOD;
        f[i] = (i - 1) * (i - 1) * 2 % MOD * f[i - 2] % MOD;
        (f[i] += (i - 1) * (i - 2) * 2 % MOD * f[i - 2] % MOD) %= MOD;
        if(i >= 3) (f[i] += (i - 1) * (i - 2) % MOD * 8 % MOD * (i - 2) % MOD * f[i - 3] % MOD) %= MOD;
        f[i] = f[i] * tmp % MOD * 4 % MOD;
    }

    cin >> T;
    while(T--) {
        cin >> n >> k;
        int ans = C(n, k) * C(n, k) % MOD * pw2[k] % MOD * fact[k] % MOD * f[n - k] % MOD;
        cout << ans << '\n';
    }

    return 0;
}