30.多项式与生成函数
多项式与生成函数
生成函数
母函数(又翻为生成函数),是求解递推关系巧妙的数学方法,他通过代数手段解决组合计数问题
先思考一个简单的问题:从数字

我们构造一个多项式,把他展开
惊人的发现,每个数的组合数量和系数一样
母函数的作用就是这样:把组合问题的加法与幂级数的乘幂对应起来
普通母函数的定义:对于序列
母函数的实质是无限可微分函数的泰勒计数,有:
利用母函数的泰勒计数表达,有时可以简单的演算组合计数问题
在袋子中装
写出生成函数:
最后一步根据牛顿二项式定理得到
幂级数
- 多项式:
- 形式幂级数:
形式幂级数之间可以有一些运算:
设
- 加法:
- 乘法:
我们发现,乘法和多项式乘法有共同之处
形式幂级数
记形式幂级数(或多项式)
形式幂级数的本质是序列,幂级数的本质是极限
形式幂级数的逆元
- 形式幂级数
的逆元: - 逆元存在的条件:
常见的逆:
的逆元为 的逆元为 的逆元为 ,即
形式幂级数的求导
假设
定义
形式幂级数的积分
假设
定义
形式幂级数的复合
假设

形式幂级数的其他运算
- 设形式幂级数
满足 由此可以定义 和 ,或者可以定义 则可以计算

常生成函数
一个数列
例: 有两种物品,其中取
个第 种物品的方案数为 ,取 个第二种物体的方案数为 ,求取 个物体的方案数
设
答案为
递推关系
斐波那契数列满足
,求其生成函数
设
解得
我们可以通过这个常生成函数来求数列的第
把分数拆开,有:
那么:
上面的
指数生成函数
一个数列
有两个物体,其中取
个第 种物品的方案数为 ,取 个第二种物体的方案数为 ,求取 个物体并排成一列的方案数
设
所以答案为
使用
FFT
朴素多项式乘法,求逆,开根号等的复杂度为
FFT / NTT 可以
多项式的表示形式
假设
则
那么也就是说
在点值表示下
复数与单位根
复数的指数形式
,其中 ,
单位根
在复数域上的根称为 次单位根。 次单位根有 个,形式为
单位根的性质:
快速傅里叶变换
DFT(离散傅里叶变换)
将多项式
转化为其点值形式
这里如何求这个
这里有傅里叶变换的公式:
由于这个式子是一个线性的,所以我们能写成矩阵的形式
但是我们暴力求
我们有:
把
设
则
- 当
时
- 当
时
不妨把
IDFT(逆离散傅里叶变换)
将多项式的点值表示
, 转化为其系数表示
我直接给出逆变换的公式
但是现在要思考如何理解这个公式
对于上面那个 DFT 的变换的矩阵
现在相当于已知
尝试构造
观察答案矩阵
我们发现这是一个公差为
得出答案矩阵为:
这样就可以求出
现在我们已知了 DFT 和 IDFT 的公式,那么我们就可以进行 FFT 了
直接来看代码实现
首先,我们需要一个复数类,尽管 C++ 有复数类,常数太大了
struct Complex {
double x, y;
Complex (double x = 0, double y = 0) : x(x), y(y) {}
};
Complex operator + (Complex a, Complex b) { return Complex(a.x + b.x, a.y + b.y); }
Complex operator - (Complex a, Complex b) { return Complex(a.x - b.x, a.y - b.y); }
Complex operator * (Complex a, Complex b) { return Complex(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); }
递归实现 FFT
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 22;
const double eps = 1e-6, pi = acos(-1);
complex<double> a[MAXN], b[MAXN], c[MAXN];
int n, m;
void FFT(complex<double> *a, int n, int inv) { // inv 为 1 时为 FFT, inv 为 -1 时为 IFFT
if (n == 1) return;
int mid = n >> 1;
complex<double> A1[mid + 1], A2[mid + 1];
for (int i = 0; i <= n; i += 2) {
A1[i >> 1] = a[i];
A2[i >> 1] = a[i + 1];
}
FFT(A1, mid, inv); FFT(A2, mid, inv);
complex<double> w0(1, 0), wn(cos(2 * pi / n), inv * sin(2 * pi / n));
for (int i = 0; i < mid; i++, w0 *= wn) {
a[i] = A1[i] + w0 * A2[i];
a[i + mid] = A1[i] - w0 * A2[i];
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i <= n; i++) {double x; scanf("%lf", &x); a[i].real(x);}
for (int i = 0; i <= m; i++) {double x; scanf("%lf", &x); b[i].real(x);}
int len = 1 << max((int)ceil(log2(n + m)), 1); //由于FFT需要项数为2的整数次方倍,所以多项式的次数len为第一个大于 n+m 的二的正整数次方
FFT(a, len, 1); FFT(b, len, 1);
for (int i = 0; i < len; i++) c[i] = a[i] * b[i];
FFT(c, len, -1);
for (int i = 0; i <= n + m; i++)
printf("%.0f ",(c[i].real() / len + eps));
return 0;
}
蝴蝶变换
观察原序列和后序列的二进制之后,惊奇的发现,其实际上就是把二进制翻转了,我们可以用
实际上可以用
通过这个递推式能求出
void change(complex<double> A[], int n) {
for (int i = 0; i < n; i++)
R[i] = (R[i >> 1] >> 1) + ((i & 1) ? n >> 1 : 0);
for (int i = 0; i < n; i++)
if (i < R[i]) swap(A[i], A[R[i]]); // 只需要换一次
}
然后有了这个思路,就可以直接构造出最后一层的下标然后从下往上模拟递归合并的过程了
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 22;
const double eps = 1e-6, pi = acos(-1);
complex<double> a[MAXN], b[MAXN], c[MAXN];
int R[MAXN];
int n, m;
void change(complex<double> A[], int n) {
for (int i = 0; i < n; i++)
R[i] = (R[i >> 1] >> 1) + ((i & 1) ? n >> 1 : 0);
for (int i = 0; i < n; i++)
if (i < R[i]) swap(A[i], A[R[i]]);
}
void FFT(complex<double> *a, int n, int inv) { // inv 为 1 时为 FFT, inv 为 -1 时为 IFFT
change(a, n);
for (int m = 2; m <= n; m <<= 1) {
complex<double> w1(cos(2 * pi / m), inv * sin(2 * pi / m));
for (int i = 0; i < n; i += m) {
complex<double> wk(1, 0);
for (int j = 0; j < m / 2; j++, wk *= w1) {
auto x = a[i + j], y = wk * a[i + j + m / 2];
a[i + j] = x + y; a[i + j + m / 2] = x - y;
}
}
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i <= n; i++) {double x; scanf("%lf", &x); a[i].real(x);}
for (int i = 0; i <= m; i++) {double x; scanf("%lf", &x); b[i].real(x);}
int len = 1 << max((int)ceil(log2(n + m)), 1); //由于FFT需要项数为2的整数次方倍,所以多项式的次数len为第一个大于 n+m 的二的正整数次方
FFT(a, len, 1); FFT(b, len, 1);
for (int i = 0; i < len; i++) c[i] = a[i] * b[i];
FFT(c, len, -1);
for (int i = 0; i <= n + m; i++)
printf("%.0f ",(c[i].real() / len + eps));
return 0;
}
NTT
原根
欧拉定理
若正整数
,满足 ,则
阶
若正整数
,满足 ,则 的最小正整数 称为 对模 的阶,记作
若
,则称 为 的一个原根
阶的性质
假设
在模 意义下两两不同
原根的存在与判定
只有模
存在原根( 是奇质数)
原根的判定定理
设
, 为正整数且 。则 是 的原根当且仅当对于任意 的质因子 ,
如何寻找原根,假设这里我们需要寻找
一个数原根的个数为
若
int rt = 1; // P 是一个质数
while (true) {
bool ok = true;
int v = 1;
for (int i = 1; i < P - 1; i++) {
v = v * rt % P;
if (v == 1) {
ok = false;
break;
}
}
if (ok) break;
rt += 1;
}
设
于是,我们只需要预处理出
指标
对于质数
,假设 是 的一个原根,则 在模 意义下是 的一个排列。假设对于 有 则称 的指标为 ,记作
指标有着非常优秀的性质,也被称为离散对数:
如何求一个
求所有的
vector<ll> lg(P, 0);
for (int i = 0, v = 1; i < P - 1; i++) {
lg[v] = i;
v = v * rt % P;
}
NTT 实现
假设质数
我们发现,这样子去选取
和 FFT 类似,只不过这里用原根来替换单位根,来进行一个模意义下的运算,FFT 中的 DFT,IDFT,依然成立
- NTT 的优点:快,精确
- NTT 的限制:模数需要是满足
的质数 ,
下面有些常见的模数:
非递归的 NTT
//NTT
int rev[MAXN], limit, bit;
void NTT (vector<ll> &a, int op) {
for (int i = 0; i < limit; i++)
if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int m = 2; m <= limit; m <<= 1) {
ll gn = qpow(3, (MOD - 1) / m);
if (op == -1) gn = qpow(gn, MOD - 2);
for (int i = 0; i < limit; i += m) {
ll gk = 1;
for (int j = 0; j < m / 2; j++) {
ll x = a[i + j], y = gk * a[i + j + m / 2] % MOD;
a[i + j] = (x + y) % MOD;
a[i + j + m / 2] = (x - y + MOD) % MOD;
gk = gk * gn % MOD;
}
}
}
}
void convolution(vector<ll> A, vector<ll> B, vector<ll> &C) {
int n = A.size() - 1, m = B.size() - 1;
limit = 1, bit = 0;
while (limit <= n + m) limit <<= 1, bit += 1;
for (int i = 0; i < limit; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
A.resize(limit), B.resize(limit); C.resize(limit);
for (int i = n + 1; i < limit; i++) A[i] = 0;
for (int i = m + 1; i < limit; i++) B[i] = 0;
NTT(A, 1), NTT(B, 1);
for (int i = 0; i < limit; i++)
C[i] = A[i] * B[i] % MOD;
NTT(C, -1);
ll inv = qpow(limit, MOD - 2);
for (int i = 0; i < limit; i++)
C[i] = C[i] * inv % MOD;
}
牛顿迭代
给定多项式
当
假设现在已经得到了
有递归式:
多项式求逆
给定函数
应用牛顿迭代可得:
于是,我们就得到了
根据主定理,时间复杂度为
多项式开方
设给定函数为
应用牛顿迭代可得:
时间复杂度
多项式 exp
给定函数
应用牛顿迭代可得:
时间复杂度为
拉格朗日差值
拉格朗日差值定理用于点值表示转化成多项式形式,我们需要构造出一个多项式,满足
首先,把
特别的,我们可以令
那么很显然可以构造出
回代:
则:
考虑计算
- 特殊情况一:
只需要求一个
- 特殊情况二:
只需要求一个