你以为你以为的就是你以为的吗?
所有多项式操作的时间复杂度都是 O ( n log n ) O(n \log n) O(nlogn)的。
分析略。
乘法: ~50ms
求逆:~100ms
开根:~250ms
Exp:~400ms
已知多项式 F ( x ) F(x) F(x),求 G ( x ) G(x) G(x)使得 F ( x ) × G ( x ) = 1 F(x)\times G(x)=1 F(x)×G(x)=1。
注意到 G G G可逐位确定。 O ( n 2 ) O(n^2) O(n2)可暴力。
容易发现:
举个例子:
( 1 − x ) × ( 1 + x + x 2 + . . . + x n + . . . ) = 1 (1-x)\times(1+x+x^2+...+x^n+...)=1 (1−x)×(1+x+x2+...+xn+...)=1
这两个多项式互为逆。
A n A_n An是在模 x n x^n xn意义下的多项式A。
G F n = 1 GF_n=1 GFn=1(在模 x n x^n xn下成立)
2 G F n − G 2 F n 2 = 1 2GF_n-G^2F_n^2=1 2GFn−G2Fn2=1(此处平方,变成了在模 x 2 n x^{2n} x2n成立)
G ( 2 F n − G F n 2 ) = 1 G(2F_n-GF_n^2)=1 G(2Fn−GFn2)=1
因此可以倍增。
注意,中途的次数界要开到求逆次数的两倍。
F 2 ( x ) = G ( x ) F^2(x)=G(x) F2(x)=G(x),则F(x)是G(x)的开方。
也可用
F n = F 2 n F_n=F_{2n} Fn=F2n,在模x^n意义下
F n 2 + 2 F n F 2 n + F 2 n 2 = 0 F_n^2 + 2 F_nF_{2n}+F_{2n}^2=0 Fn2+2FnF2n+F2n2=0,在模x^2n意义下。
由于在模x^2n意义下 F 2 n = G F_{2n}=G F2n=G
F n 2 + 2 F n F 2 n + G = 0 F_n^2 + 2 F_nF_{2n}+G=0 Fn2+2FnF2n+G=0
即求得 F 2 n F_{2n} F2n。
若F(x)是常数项为0的指数型生成函数(EGF),则
定义: e F ( x ) = ∑ i ≥ 0 F i ( x ) / i ! e^F(x)=\sum_{i\geq0}F^i(x)/i! eF(x)=∑i≥0Fi(x)/i!
多项式的0次方被定义为1,因此 e F ( x ) e^F(x) eF(x)的常数项为1.
组合意义:对元素1…n进行集合划分, e F ( x ) e^F(x) eF(x)即为所有划分方案的egf。
具体地, [ x n / n ! ] [x^n/n!] [xn/n!]的系数即为将n个元素进行集合划分后的方案权和。
其中方案权和是指,一种集合划分方案可以被表示成集合的集合 A A A,则 权 ( A ) = ∏ B ∈ A [ x ∣ B ∣ ] F 权(A)=\prod_{B\in A} [x^{|B|}]F 权(A)=∏B∈A[x∣B∣]F
举个例子:若 F ( x ) F(x) F(x)是带标号无向连通图方案数的EGF,则 G ( x ) = e F ( x ) G(x)=e^{F(x)} G(x)=eF(x),其中 G ( x ) G(x) G(x)是无向简单图的方案数的EGF。
即解方程 f ( x ) = l n ( x ) − A f(x)=ln(x)-A f(x)=ln(x)−A。
倍增求解,设已经求出了exp的前n项,设为x0. (初值:1=e^0)
对 f ( x ) f(x) f(x)在x0处泰勒展开,可以发现只有一开始两项是前2n项不全为0的。
所以 f ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) = 0 f(x)=f(x0)+f'(x0)(x-x0)=0 f(x)=f(x0)+f′(x0)(x−x0)=0,解得 x = x 0 − f ( x 0 ) / f ′ ( x 0 ) x=x0-f(x0)/f'(x0) x=x0−f(x0)/f′(x0).
f ( x ) f(x) f(x)是个多项式对多项式的函数,因此 f ′ ( x ) = 1 / x f'(x)=1/x f′(x)=1/x
最终结果是 x = x 0 ( 1 − ln x 0 + A ) x=x0(1-\ln {x0}+A) x=x0(1−lnx0+A).
ln x 0 \ln x0 lnx0有无穷项,但是我们只需要2n项
注意,对于常数项不为0的多项式无法定义Exp.
若G(x)为常数项为1的指数型生成函数,则
定义:若 e F ( x ) = G ( x ) e^{F(x)}=G(x) eF(x)=G(x),则 F ( x ) = ln G ( x ) F(x)=\ln G(x) F(x)=lnG(x)
即为exp的逆运算。
运用这个操作,可以快速解决“连通图计数”一类问题。
ln ′ ( G ( x ) ) = G ′ ( x ) G ( x ) \ln'(G(x))=\frac {G'(x)} {G(x)} ln′(G(x))=G(x)G′(x)
求逆,乘法,积分即可。常数项默认为0.
至于为什么是对x求导,而不是对G(x)求导,此处作者囿于知识水平无法给出答案。
(我高中数学都还没学到导数.jpg)
注意,对于常数项不为1的多项式,不存在ln.
快速幂: O ( n log 2 n ) O(n\log^2n) O(nlog2n)
对于常数项为1的多项式(也就是其ln存在的多项式),ln + exp: O ( n log n ) O(n \log n) O(nlogn)
假如你有一个多项式 T T T,你希望求出 T k = e x p ( k × l n ( T ) ) T^k=exp(k\times ln(T)) Tk=exp(k×ln(T))。
我们已知这样一个关系:若 F ( x ) F(x) F(x)是带标号无向连通图方案数的EGF,则 G ( x ) = e F ( x ) G(x)=e^{F(x)} G(x)=eF(x),其中 G ( x ) G(x) G(x)是无向简单图的方案数的EGF。
那么,我们知道G和F对应的OGF的递推关系。为了求 e T e^T eT,先将T变成对应的OGF,然后做递推得到 e T e^T eT的OGF,然后再变回EGF即可。
for(int i = 0; i <= n; i++) G[i] = G[i] * jc[i] % mo;
for(int i = 1; i <= n; i++) {
F[i] = G[i];
for(int j = 1; j <= i - 1; j++) {
F[i] -= C[i-1][j-1] * G[i - j] * F[j];
}
}
for(int i = 0; i <= n; i++) F[i] = F[i] * 2;
F[0] = 0;
memset(G,0,sizeof G); G[0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= i; j++) {
G[i] += F[j] * C[i - 1][j - 1] * G[i - j];
}
}
for(int i = 0; i <= n; i++) cout << G[i] * njc[i] % mo << " ";
若有两多项式 F , G F,G F,G,满足 F ( G ( x ) ) = x F(G(x))=x F(G(x))=x,则称其互为复合逆。
定理: F ( G ( x ) ) = G ( F ( x ) ) F(G(x))=G(F(x)) F(G(x))=G(F(x))。
因此通常写作 ( G ⋅ F ) ( x ) (G\cdot F)(x) (G⋅F)(x)
满足交换律与左右结合律。
相关链接:
拉格朗日反演及其扩展(这辈子都不会用到,笑)
待填
实现丑陋,随便封装了一下。
typedef long long ll;
typedef vector<ll> poly;
const int N = 262244, mo = 998244353;
namespace Poly {
ll w[N], Mx, M;
ll jc[N], njc[N], inv[N];
namespace PM {
ll ksm(ll x, ll y) {
ll ret = 1;
for (; y; y >>= 1) {
if (y & 1) ret = ret * x % mo;
x = x * x % mo;
}
return ret;
}
void DFT(poly &a) {
assert(a.size() <= M);
a.resize(M);
static int h[N];
for(int i = 0; i < M; i++) {
h[i] = (h[i >> 1] >> 1) + (i & 1) * (M >> 1);
if(i < h[i]) swap(a[i], a[h[i]]);
}
for(int m = 1; m < M; m <<= 1) {
int step = Mx / (m << 1);
for(int i = 0; i < M; i += (m << 1)) {
for(int j = 0; j < m; j++) {
ll u = a[i + j + m] * w[step * j];
a[i + j + m] = (a[i + j] - u) % mo;
a[i + j] = (a[i + j] + u) % mo;
}
}
}
}
void IDFT(poly &a) {
reverse(a.begin() + 1, a.end());
DFT(a);
for(int i = 0; i < M; i++) a[i] = a[i] * inv[M] % mo;
}
poly operator * (poly a, poly b) {
int sz = a.size() + b.size() - 1;
for(M = 1; M < sz; M <<= 1);
DFT(a), DFT(b);
for(int i = 0; i < M; i++) a[i] = a[i] * b[i] % mo;
IDFT(a);
a.resize(sz);
return a;
}
poly operator + (poly a, poly b) {
int n = max(a.size(), b.size());
a.resize(n), b.resize(n);
for(int i = 0; i < n; i++) a[i] = (a[i] + b[i]) % mo;
return a;
}
poly operator - (poly a, poly b) {
int n = max(a.size(), b.size());
a.resize(n), b.resize(n);
for(int i = 0; i < n; i++) a[i] = (a[i] - b[i]) % mo;
return a;
}
poly _Inv(poly a, int n) {
a.resize(n);
if (n == 1) {
a[0] = ksm(a[0], mo - 2);
return a;
}
poly b = _Inv(a, n >> 1);
M = n * 2;
DFT(a), DFT(b);
for(int i = 0; i < M; i++) {
a[i] = b[i] * (2 - a[i] * b[i] % mo) % mo;
}
IDFT(a);
a.resize(n); return a;
}
//在模x^m意义下的逆元
poly Inv(poly a, int n) {
int Z; for(Z = 1; Z < n; Z <<= 1);
a = _Inv(a, Z);
a.resize(n); return a;
}
//求导
poly Der(poly a) {
for(int i = 0, n = a.size() - 1; i < n; i++) {
a[i] = a[i + 1] * (i + 1) % mo;
}
a.pop_back();
return a;
}
//积分
poly Int(poly a) {
a.push_back(0);
for(int i = a.size() - 1; i; i--) {
a[i] = a[i - 1] * inv[i] % mo;
}
a[0] = 0;
return a;
}
poly Ln(poly a, int n) {
a[0] = (a[0] + mo) % mo;
assert(a[0] == 1);
a.resize(n); a = Der(a) * Inv(a, n);
a.resize(n); a = Int(a);
a.resize(n); return a;
}
poly _Exp(poly a, int n) {
a.resize(n);
if (n == 1) {
a[0] = 1; return a;
}
poly F0 = _Exp(a, n >> 1);
poly b = a - Ln(F0, n); b[0] ++;
F0 = F0 * b;
F0.resize(n);
return F0;
}
poly Exp(poly a, int n) {
assert(a[0] == 0);
int Z; for(Z = 1; Z < n; Z <<= 1);
a = _Exp(a, Z);
a.resize(n); return a;
}
poly Pow(poly x, int y) {
x = Ln(x, x.size());
for(int i = x.size() - 1; ~i; i--)
x[i] = x[i] * y % mo;
return Exp(x, x.size());
}
poly shift(poly a) {
a.push_back(0);
for(int i = a.size() - 1; i; i--)
a[i] = a[i - 1];
a[0] = 0;
return a;
}
poly shiftLeft(poly a) {
for(int i = 0; i < a.size(); i++)
a[i] = a[i + 1];
a.pop_back();
return a;
}
}
void init(int n) {
for(Mx = 1; Mx <= 2 * n; Mx <<=1);
w[0] = 1;
w[1] = PM :: ksm(3, (mo - 1) / Mx);
for(int i = 2; i < Mx; i++) w[i] = w[i - 1] * w[1] % mo;
jc[0] = 1;
for(int i = 1; i <= Mx; i++) jc[i] = jc[i - 1] * i % mo;
njc[Mx] = PM :: ksm(jc[Mx], mo - 2);
for(int i = Mx - 1; ~i; i--) njc[i] = njc[i + 1] * (i + 1) % mo;
for(int i = 1; i <= Mx; i++) inv[i] = jc[i - 1] * njc[i] % mo;
}
}