多项式
拉格朗日插值
- 已知最高次数不超过
n
−
1
n - 1
n−1 的多项式在平面上的
n
n
n 个点
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
…
,
(
x
n
,
y
n
)
(x_1,y_1),(x_2,y_2),\dots,(x_n,y_n)
(x1,y1),(x2,y2),…,(xn,yn),满足
∀
1
≤
i
<
j
≤
n
,
x
i
≠
x
j
\forall 1 \le i < j \le n, x_i\not=x_j
∀1≤i<j≤n,xi=xj,则可还原出多项式:
f ( x ) = ∑ i = 1 n y i ∏ j ≠ i x − x j x i − x j f(x) = \sum\limits_{i = 1}^{n}y_i\prod\limits_{j \not=i} \frac{x - x_j}{x_i - x_j} f(x)=i=1∑nyij=i∏xi−xjx−xj - 若
x
i
=
i
x_i = i
xi=i,且
x
>
n
x > n
x>n,则该式可简化为:
f ( x ) = ( ∏ i = 1 n ( x − i ) ) ( ∑ i = 1 n ( − 1 ) n − i y i ( x − i ) ( i − 1 ) ! ( n − i ) ! ) f(x) = \left(\prod\limits_{i = 1}^{n}(x - i)\right)\left(\sum\limits_{i = 1}^{n}\frac{(-1)^{n-i}y_i}{(x-i)(i-1)!(n - i)!} \right) f(x)=(i=1∏n(x−i))(i=1∑n(x−i)(i−1)!(n−i)!(−1)n−iyi) - 预处理相关逆元和前缀积,即可 O ( n ) \mathcal O(n) O(n) 计算。
- 若已知二元多项式在空间内的
n
m
nm
nm 个点
(
x
11
,
y
11
,
z
11
)
,
…
,
(
x
n
m
,
y
n
m
,
z
n
m
)
(x_{11},y_{11},z_{11}),\dots,(x_{nm},y_{nm},z_{nm})
(x11,y11,z11),…,(xnm,ynm,znm),满足任意两点横纵坐标至少一个不相等,且该多项式中
x
x
x 的最高次数不超过
n
−
1
n - 1
n−1,
y
y
y 的最高次数不超过
m
−
1
m - 1
m−1,则可还原出多项式:
f ( x , y ) = ∑ i = 1 n ∑ j = 1 m z i j ∏ k ≠ i x − x k j x i j − x k j ∏ l ≠ j x − x i l x i j − x i l f(x,y) = \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{m}z_{ij}\prod\limits_{k\not=i}\frac{x-x_{kj}}{x_{ij}-x_{kj}}\prod\limits_{l\not = j}\frac{x-x_{il}}{x_{ij}-x_{il}} f(x,y)=i=1∑nj=1∑mzijk=i∏xij−xkjx−xkjl=j∏xij−xilx−xil
FFT
- 即快速傅里叶变换。
- 设 n n n 次单位根 ω n k = cos 2 π k n + i sin 2 π k n \omega_{n}^k = \cos\frac{2\pi k}{n} + i\sin\frac{2\pi k}{n} ωnk=cosn2πk+isinn2πk。
- 将多项式 A , B A,B A,B 的项数补到 2 的整数次幂,设项数为 n n n,采用分治法快速求出 A , B A,B A,B 代入 ω n 0 , ω n 1 , … , ω n n − 1 \omega_{n}^0, \omega^1_n, \dots, \omega^{n - 1}_{n} ωn0,ωn1,…,ωnn−1 的点值,将点值相乘再通过类似的过程还原回多项式,即可快速求出多项式 A × B A \times B A×B,时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
DFT
- 即离散傅里叶变换。
- 对于多项式
A ( x ) = ∑ k = 0 n − 1 a k x k = ∑ k = 0 n 2 − 1 ( a 2 k x 2 k + a 2 k + 1 x 2 k + 1 ) = ∑ k = 0 n 2 − 1 a 2 k ( x 2 ) k + x ∑ k = 0 n 2 − 1 a 2 k + 1 ( x 2 ) k A(x) = \sum\limits_{k = 0}^{n - 1}a_k x^k = \sum \limits_{k = 0}^{\frac{n}{2} - 1}(a_{2k}x^{2k} + a_{2k + 1}x^{2k + 1}) = \sum \limits_{k = 0}^{\frac{n}{2} - 1}a_{2k}(x^{2})^k + x\sum\limits_{k = 0}^{\frac{n}{2} - 1}a_{2k+1}(x^2)^k A(x)=k=0∑n−1akxk=k=0∑2n−1(a2kx2k+a2k+1x2k+1)=k=0∑2n−1a2k(x2)k+xk=0∑2n−1a2k+1(x2)k - 设
A
1
(
x
)
=
∑
k
=
0
n
2
−
1
a
2
k
x
k
,
A
2
(
x
)
=
∑
i
=
0
n
2
−
1
a
2
k
+
1
x
k
A_1(x) = \sum \limits_{k = 0}^{\frac{n}{2} - 1}a_{2k}x^k, A_2(x) = \sum\limits_{i = 0}^{\frac{n}{2}-1}a_{2k + 1}x^k
A1(x)=k=0∑2n−1a2kxk,A2(x)=i=0∑2n−1a2k+1xk,若已求得
A
1
(
ω
n
2
k
)
,
A
2
(
ω
n
2
k
)
,
0
≤
k
≤
n
2
−
1
A_1(\omega_\frac{n}{2}^k), A_2(\omega^{k}_{\frac{n}{2}}), 0 \le k \le \frac{n}{2} - 1
A1(ω2nk),A2(ω2nk),0≤k≤2n−1,则
A ( ω n k ) = A 1 ( ω n 2 k ) + w n k A 2 ( w n 2 k ) = A 1 ( ω n 2 k ) + w n k A 2 ( ω n 2 k ) A ( ω n k + n 2 ) = A 1 ( ω n 2 k ) − w n k + n 2 A 2 ( w n 2 k ) = A 1 ( ω n 2 k ) − w n k A 2 ( ω n 2 k ) A(\omega_n^k) = A_1(\omega^{2k}_{n}) + w_{n}^{k}A_2(w_{n}^{2k}) = A_1(\omega^{k}_{\frac{n}{2}}) + w_{n}^{k}A_2(\omega^{k}_{\frac{n}{2}}) \\ A(\omega_n^{k + \frac{n}{2}}) = A_1(\omega^{2k}_{n}) - w_{n}^{k+\frac{n}{2}}A_2(w_{n}^{2k}) = A_1(\omega^{k}_{\frac{n}{2}}) -w_{n}^{k}A_2(\omega^{k}_{\frac{n}{2}}) \\ A(ωnk)=A1(ωn2k)+wnkA2(wn2k)=A1(ω2nk)+wnkA2(ω2nk)A(ωnk+2n)=A1(ωn2k)−wnk+2nA2(wn2k)=A1(ω2nk)−wnkA2(ω2nk) - 不断分治下去,此时我们便求得了 A ( ω n k ) , 0 ≤ k ≤ n − 1 A(\omega^{k}_n), 0 \le k \le n - 1 A(ωnk),0≤k≤n−1。
IDFT
- 即逆离散傅里叶变换,由之前的叙述我们有:
[ ( ω n 0 ) 0 ( ω n 0 ) 1 ⋯ ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 ⋯ ( ω n 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n n − 1 ) 0 ( ω n n − 1 ) 1 ⋯ ( ω n n − 1 ) n − 1 ] [ a 0 a 1 ⋮ a n − 1 ] = [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[ \begin{matrix} (\omega_n^0)^0 & (\omega_n^0)^1 & \cdots & (\omega^0_n)^{n - 1}\\ (\omega_n^1)^0 & (\omega_n^1)^1 & \cdots & (\omega^1_n)^{n - 1}\\ \vdots & \vdots & \ddots & \vdots\\ (\omega_n^{n - 1})^0 & (\omega_n^{n-1})^1 & \cdots & (\omega^{n - 1}_n)^{n - 1}\\ \end{matrix} \right] \left[ \begin{matrix} a_0 \\ a_1 \\ \vdots \\ a_{n - 1} \\ \end{matrix} \right] = \left[ \begin{matrix} A(\omega_n^0)\\ A(\omega_n^1) \\ \vdots \\ A(\omega_n^{n - 1}) \\ \end{matrix} \right] (ωn0)0(ωn1)0⋮(ωnn−1)0(ωn0)1(ωn1)1⋮(ωnn−1)1⋯⋯⋱⋯(ωn0)n−1(ωn1)n−1⋮(ωnn−1)n−1 a0a1⋮an−1 = A(ωn0)A(ωn1)⋮A(ωnn−1) - 对矩阵求逆,可得:
[ a 0 a 1 ⋮ a n − 1 ] = 1 n [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 ⋯ ( ω n − 0 ) n − 1 ( ω n − 1 ) ) 0 ( ω n − 1 ) 1 ⋯ ( ω n − 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n − ( n − 1 ) ) 0 ( ω n − ( n − 1 ) ) 1 ⋯ ( ω n − ( n − 1 ) ) n − 1 ] [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[ \begin{matrix} a_0 \\ a_1 \\ \vdots \\ a_{n - 1} \\ \end{matrix} \right] = \frac{1}{n} \left[ \begin{matrix} (\omega_n^{-0})^0 & (\omega_n^{-0})^1 & \cdots & (\omega^{-0}_n)^{n - 1}\\ (\omega_n^{-1}))^0 & (\omega_n^{-1})^1 & \cdots & (\omega^{-1}_n)^{n - 1}\\ \vdots & \vdots & \ddots & \vdots\\ (\omega_n^{-(n - 1)})^0 & (\omega_n^{-(n-1)})^1 & \cdots & (\omega^{-(n - 1)}_n)^{n - 1}\\ \end{matrix} \right] \left[ \begin{matrix} A(\omega_n^0)\\ A(\omega_n^1) \\ \vdots \\ A(\omega_n^{n - 1}) \\ \end{matrix} \right] a0a1⋮an−1 =n1 (ωn−0)0(ωn−1))0⋮(ωn−(n−1))0(ωn−0)1(ωn−1)1⋮(ωn−(n−1))1⋯⋯⋱⋯(ωn−0)n−1(ωn−1)n−1⋮(ωn−(n−1))n−1 A(ωn0)A(ωn1)⋮A(ωnn−1) - 不难发现,将 ω n k \omega_n^k ωnk 替换成 ω n − k \omega_n^{-k} ωn−k 再做一遍 DFT,最后将结果除以 n n n 即可实现 IDFT。
位逆序变换
- 为了减小递归实现带来的常数,我们考虑直接得到递归最底层的排列顺序,再逐层向上合并。
- 不难证明,设 n = 2 m n = 2^m n=2m,则系数 a i a_i ai 递归到最底层时的下标恰好为 i i i 在 m m m 位二进制表示下的对称翻转,我们称这个变换为位逆序变换(蝴蝶变换)。
- 设 r e v [ i ] rev[i] rev[i] 表示系数 a i a_i ai 递归到最底层时的下标,则显然有递推式
int m = 0, _n = na + nb; //相乘的两个多项式最高次数分别为 na,nb
for (n = 1; n <= _n; n <<= 1)
++m;
for (int i = 1; i < n; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (m - 1));
- 最终我们得到了 FFT 的迭代实现。
typedef long double ld;
typedef complex<ld> com;
const ld pi = acos(-1.0);
inline void DFT(vector<com> &a, int opt)
{
int n = a.size();
for (int i = 0; i < n; ++i)
if (i < rev[i])
std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1)
{
com w(cos(pi / k), opt * sin(pi / k));;
for (int i = 0; i < n; i += k << 1)
{
com res(1.0, 0.0);
for (int j = 0; j < k; ++j)
{
com u = a[i + j],
v = res * a[i + j + k];
a[i + j] = u + v;
a[i + j + k] = u - v;
res = res * w;
}
}
}
if (opt == -1)
{
for (int i = 0; i < n; ++i)
a[i] /= n;
}
}
NTT
- 即快速数论变换。
- 在模质数
P
P
P 意义下,原根
g
g
g 具有单位根的性质:
( g k ) P − 1 ≡ 1 ( m o d P ) , g P − 1 2 ≡ − 1 ( m o d P ) (g^k)^{P - 1} \equiv 1(\mod P), g^{\frac{P - 1}{2}}\equiv -1(\mod P) (gk)P−1≡1(modP),g2P−1≡−1(modP) - 若 P = 2 x a + 1 P = 2^xa +1 P=2xa+1,设 n = 2 m n = 2^m n=2m,用 g 2 x − m a g^{2^{x - m}a} g2x−ma 替换 ω n \omega_n ωn 即可实现 NTT。
- 常见的
P
P
P 有:
P = 1004535809 = 479 × 2 21 + 1 , g = 3 P = 998244353 = 7 × 17 × 2 23 + 1 , g = 3 P = 1004535809 = 479 \times 2 ^ {21} + 1, g = 3 \\ P = 998244353 = 7 \times 17 \times 2^{23} + 1, g = 3 P=1004535809=479×221+1,g=3P=998244353=7×17×223+1,g=3 - 实现时可以预处理 g 2 x − m a g^{2^{x - m}a} g2x−ma 的幂次,减小常数,代码见多项式基本操作。
多项式基本操作
- 相关数组应开到题目给定长度的四倍,线性逆元应预处理到题目给定长度的两倍。
快速卷积的变式
- 欲求
h
k
=
∑
i
=
0
n
−
k
f
i
g
i
+
k
h_k = \sum \limits_{i = 0}^{n - k}f_ig_{i+k}
hk=i=0∑n−kfigi+k,令
g
n
−
i
−
k
′
=
g
i
+
k
,
h
n
−
k
′
=
h
k
g'_{n - i - k} = g_{i + k}, h'_{n - k} = h_k
gn−i−k′=gi+k,hn−k′=hk,则原式可变为:
h n − k ′ = ∑ i = 0 n − k f i g n − k − i ′ h'_{n - k} = \sum \limits_{i = 0}^{n - k}f_ig'_{n - k -i} hn−k′=i=0∑n−kfign−k−i′ - 做快速卷积即可。
分治NTT
- 以计算 f i = ∑ j = 1 i f i − j g j ( 1 ≤ i < n ) f_i = \sum\limits_{j = 1}^{i}f_{i -j}g_j(1 \le i < n) fi=j=1∑ifi−jgj(1≤i<n) 为例, g 1 , g 2 , … , g n − 1 g_1, g_2, \dots, g_{n - 1} g1,g2,…,gn−1 已知, f 0 = 1 f_0 = 1 f0=1。
- 对于当前区间
[
l
,
r
]
[l,r]
[l,r],取中点
m
i
d
mid
mid。
- 先递归区间 [ l , m i d ] [l, mid] [l,mid]。
- 做 g 1 , … , g r − l g_1, \dots, g_{r - l} g1,…,gr−l 和 f l , f l + 1 , … , f m i d f_{l}, f_{l + 1},\dots,f_{mid} fl,fl+1,…,fmid 的卷积,求出两者对 f m i d + 1 , f m i d + 2 , … , f r f_{mid + 1}, f_{mid + 2}, \dots, f_r fmid+1,fmid+2,…,fr 的贡献。
- 再递归区间 [ m i d + 1 , r ] [mid + 1, r] [mid+1,r]。
- 时间复杂度 O ( n log 2 n ) \mathcal O(n\log^2n) O(nlog2n)。
牛顿迭代
- 当前有一个函数 g g g,要求解出一个多项式 f f f 的前 n n n 项,使得 g ( f ) = 0 g(f) = 0 g(f)=0。
- 已知
f
f
f 的前
n
n
n 项
f
0
f_0
f0,现在想求出
f
f
f 的前
2
n
2n
2n 项,由泰勒展开:
0 ≡ g ( f 0 ) + g ′ ( f 0 ) ( f − f 0 ) ( m o d x 2 n ) f ≡ f 0 − g ( f 0 ) g ′ ( f 0 ) ( m o d x 2 n ) 0 \equiv g(f_0) + g'(f_0)(f - f_0) (\mod x^{2n}) \\ f \equiv f_0 - \frac{g(f_0)}{g'(f_0)}(\mod x^{2n}) \\ 0≡g(f0)+g′(f0)(f−f0)(modx2n)f≡f0−g′(f0)g(f0)(modx2n)
多项式求逆
- 考虑和牛顿迭代类似的倍增法。
- 设当前求出
B
0
(
x
)
B_0(x)
B0(x) 使得
A
(
x
)
B
0
(
x
)
≡
1
(
m
o
d
x
n
)
A(x)B_0(x) \equiv 1 (\mod x^n)
A(x)B0(x)≡1(modxn),则
[ A ( x ) B 0 ( x ) − 1 ] 2 ≡ 0 ( m o d x 2 n ) A ( x ) [ 2 B 0 ( x ) − A ( x ) B 0 2 ( x ) ] ≡ 1 ( m o d x 2 n ) [A(x)B_0(x) - 1]^2 \equiv 0(\mod x^{2n}) \\ A(x)[2B_0(x) - A(x)B_0^2(x)] \equiv 1 (\mod x^{2n})\\ [A(x)B0(x)−1]2≡0(modx2n)A(x)[2B0(x)−A(x)B02(x)]≡1(modx2n) - 不断迭代下去即可,时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn)。
多项式取对数
- 对 ln ( A ( x ) ) \ln(A(x)) ln(A(x)) 求导后再积分回来,则 B ( x ) = ∫ A ′ ( x ) A ( x ) d x B(x) = \int \frac{A'(x)}{A(x)} \text{d}x B(x)=∫A(x)A′(x)dx。
- 需保证 A ( x ) A(x) A(x) 的常数项为 1。
多项式求指数
- 套用牛顿迭代,令
g
(
x
)
=
ln
(
x
)
−
A
g(x) = \ln(x) - A
g(x)=ln(x)−A,最终要使
g
(
B
)
=
0
g(B) = 0
g(B)=0,代入上式,有
B ( x ) ≡ B 0 ( x ) [ 1 − ln B 0 ( x ) + A ( x ) ] ( m o d x 2 n ) B(x) \equiv B_0(x) [1 - \ln B_0(x) + A(x)] (\mod x^{2n}) B(x)≡B0(x)[1−lnB0(x)+A(x)](modx2n) - 需保证 A ( x ) A(x) A(x) 的常数项为 0。
多项式开根
- 套用牛顿迭代,令
g
(
x
)
=
x
2
−
A
g(x) = x^2 - A
g(x)=x2−A,最终要使
g
(
B
)
=
0
g(B) = 0
g(B)=0,代入上式,有
B ( x ) ≡ B 0 ( x ) 2 + A ( x ) 2 B 0 ( x ) ( m o d x 2 n ) B(x) \equiv \frac{B_0(x)^2 + A(x)}{2B_0(x)}(\mod x^{2n}) B(x)≡2B0(x)B0(x)2+A(x)(modx2n) - 若 A ( x ) A(x) A(x) 的常数项不为 1,初始时求 B B B 的常数项需求二次剩余。
多项式快速幂
- 朴素的算法即直接倍增,时间复杂度 O ( n log 2 n ) \mathcal O(n\log^2 n) O(nlog2n)。
- 如果没有截断的情况,直接对点值求 k k k 次方也是正确的。
- 若 A ( x ) A(x) A(x) 的常数项为 1,即求 e k ln A ( x ) e^{k\ln A(x)} eklnA(x), k k k 对模数 P P P 取模。
- 若 A ( x ) A(x) A(x) 的常数项不为 1,找到第一个系数非 0 的项,设为 a 0 = [ x t ] A ( x ) a_0 = [x^t]A(x) a0=[xt]A(x),则所求变为 a 0 k x t k ( A ( x ) a 0 x t ) k a_0^kx^{tk}\left(\frac{A(x)}{a_0x^t}\right)^k a0kxtk(a0xtA(x))k,乘以 a k a^k ak 时 k k k 对 φ ( P ) = P − 1 \varphi(P) = P - 1 φ(P)=P−1 取模。
const int mod = 998244353;
const int inv2 = 499122177;
const int inv3 = 332748118;
int rev[N4], tw[N4], inv[N4]; //polyInt 中的 inv 要预处理(线性求逆元)
inline void operator += (vector<int> &a, vector<int> b)
{
int n = b.size();
a.resize(Max((int)a.size(), n));
for (int i = 0; i < n; ++i)
add(a[i], b[i]);
}
inline void operator -= (vector<int> &a, vector<int> b)
{
int n = b.size();
a.resize(Max((int)a.size(), n));
for (int i = 0; i < n; ++i)
dec(a[i], b[i]);
}
inline void operator *= (vector<int> &a, int k)
{
if (k == -1)
{
int n = a.size();
for (int i = 0; i < n; ++i)
if (a[i])
a[i] = mod - a[i];
}
else
{
int n = a.size();
for (int i = 0; i < n; ++i)
a[i] = 1ll * k * a[i] % mod;
}
}
inline void DFT(vector<int> &a, int opt)
{
int n = a.size(), g = opt == 1 ? 3 : inv3;
for (int i = 0; i < n; ++i)
if (i < rev[i])
std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1)
{
int w = quick_pow(g, (mod - 1) / (k << 1));
tw[0] = 1;
for (int j = 1; j < k; ++j)
tw[j] = 1ll * tw[j - 1] * w % mod;
for (int i = 0; i < n; i += k << 1)
{
for (int j = 0; j < k; ++j)
{
int u = a[i + j],
v = 1ll * tw[j] * a[i + j + k] % mod;
add(a[i + j] = u, v);
dec(a[i + j + k] = u, v);
}
}
}
if (opt == -1)
{
int inv_n = quick_pow(n, mod - 2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * inv_n % mod;
}
}
inline void polyMul(vector<int> &a, vector<int> b)
{
if (!a.size() || !b.size())
{
a.clear();
return ;
}
int m = 0, _n = a.size() + b.size() - 2, n;
for (n = 1; n <= _n; n <<= 1)
++m;
a.resize(n);
b.resize(n);
for (int i = 1; i < n; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (m - 1));
DFT(a, 1); DFT(b, 1);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * b[i] % mod;
DFT(a, -1);
a.resize(_n + 1);
}
inline void solve(int l, int r)
{
if (l == r)
return ;
int mid = l + r >> 1;
solve(l, mid);
vector<int> a(mid - l + 1), b(r - l + 1);
for (int i = l; i <= mid; ++i)
a[i - l] = f[i];
for (int i = 0; i < r - l + 1; ++i)
b[i] = g[i];
polyMul(a, b);
for (int i = mid + 1; i <= r; ++i)
add(f[i], a[i - l]);
solve(mid + 1, r);
}
inline void polyDer(vector<int> &a)
{
int n = a.size();
for (int i = 0; i < n; ++i)
a[i] = 1ll * (i + 1) * a[i + 1] % mod;
a.pop_back();
}
inline void polyInt(vector<int> &a)
{
int n = a.size();
a.push_back(0);
for (int i = n - 1; i >= 0; --i)
a[i + 1] = 1ll * inv[i + 1] * a[i] % mod;
a[0] = 0;
}
//以下各函数 vector<int> &b 在传入之前需保证为空
inline void polyInv(vector<int> a, vector<int> &b)
{
int n = a.size();
b.push_back(quick_pow(a[0], mod - 2));
vector<int> c;
int m = 1, m2, k;
while (m < n)
{
m <<= 1, k = 0;
for (m2 = 1; m2 < (m << 1); m2 <<= 1)
++k;
for (int i = 1; i < m2; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1));
b.resize(m2); c.resize(m2);
for (int i = 0; i < m2; ++i)
c[i] = i < n && i < m ? a[i] : 0;
DFT(b, 1); DFT(c, 1);
for (int i = 0; i < m2; ++i)
{
int tmp = b[i];
add(b[i], tmp);
dec(b[i], 1ll * c[i] * tmp % mod * tmp % mod);
}
DFT(b, -1);
b.resize(m);
}
b.resize(n);
}
/*
polyInv's example:
5
1 6 3 4 9
1 998244347 33 998244169 1020
polyLn's example:
6
1 927384623 878326372 3882 273455637 998233543
0 927384623 817976920 427326948 149643566 610586717
polyExp's example:
reversed
*/
inline void polyLn(vector<int> a, vector<int> &b)
{
int n = a.size();
polyInv(a, b);
polyDer(a);
polyMul(b, a);
b.resize(n - 1);
polyInt(b);
}
inline void polyExp(vector<int> a, vector<int> &b)
{
int n = a.size();
b.push_back(1);
vector<int> c;
int m = 1;
while (m < n)
{
m <<= 1;
c.clear();
b.resize(m);
polyLn(b, c);
for (int i = 0; i < m; ++i)
add(c[i] = mod - c[i], i < n ? a[i] : 0);
add(c[0], 1);
polyMul(b, c);
b.resize(m);
}
b.resize(n);
}
// 调用该函数前应先特判因前若干项为 0 导致结果全为 0 的情况
// k1 是指数对 mod 取模后的值, k2 是指数对 mod - 1 取模后的值
// 指数较小时两者可合并
inline void polyPow(vector<int> &a, int k1, int k2)
{
int n = a.size(), st = 0;
while (st < n && !a[st])
++st;
for (int i = st; i < n; ++i)
a[i - st] = a[i];
for (int i = n - st; i < n; ++i)
a[i] = 0;
a.resize(n);
int a0 = a[0], inv_a0 = quick_pow(a0, mod - 2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * inv_a0 % mod;
vector<int> b;
polyLn(a, b);
for (int i = 0; i < n; ++i)
b[i] = 1ll * b[i] * k1 % mod;
a.clear();
polyExp(b, a);
a.resize(n);
a0 = quick_pow(a0, k2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * a0 % mod;
for (int i = n - 1; i >= 0; --i)
if (i + 1ll * st * k1 < n)
a[i + 1ll * st * k1] = a[i];
for (int i = 0, im = Min((ll)n, 1ll * st * k1); i < im; ++i)
a[i] = 0;
}
inline void polySqrt(vector<int> a, vector<int> &b)
{
int n = a.size();
b.push_back(1);
vector<int> c, d;
int m = 1;
while (m < n)
{
m <<= 1;
b.resize(m);
c.clear();
polyInv(b, c);
d.resize(m);
for (int i = 0; i < m; ++i)
d[i] = i < n ? a[i] : 0;
polyMul(c, d);
for (int i = 0; i < m; ++i)
b[i] = 1ll * inv2 * (c[i] + b[i]) % mod;
}
b.resize(n);
}
生成函数
OGF
- 即一般生成函数 A ( x ) = ∑ i ≥ 0 a i x i A(x) = \sum \limits_{i \ge 0}a_ix^i A(x)=i≥0∑aixi。
- 常用公式: 1 1 − x = ∑ i ≥ 0 x i , 1 ( 1 − x ) 2 = ∑ i ≥ 0 ( i + 1 ) x i , 1 ( 1 − a x ) m = ∑ i ≥ 0 ( i + m − 1 m − 1 ) a i x i \frac{1}{1 - x} = \sum \limits_{i\ge 0}x^i, \frac{1}{(1-x)^2} = \sum \limits_{i \ge 0}(i + 1)x^i,\frac{1}{(1 - ax)^m} = \sum \limits_{i\ge 0}\binom{i + m - 1}{m - 1}a^ix^i 1−x1=i≥0∑xi,(1−x)21=i≥0∑(i+1)xi,(1−ax)m1=i≥0∑(m−1i+m−1)aixi
- 推导上述公式尽量用消项法或组合意义,求导/积分则较为麻烦。
- 结论 1 1 − y ( y 1 − y ) k = ∑ n ≥ 0 ( n k ) y n \dfrac{1}{1 - y}\left(\dfrac{y}{1 - y}\right)^k = \sum \limits_{n \ge 0}\binom{n}{k}y^n 1−y1(1−yy)k=n≥0∑(kn)yn
证明
A ( x , y ) = ∑ k ≥ 0 ( ∑ n ≥ 0 ( n k ) y n ) x k = ∑ n ≥ 0 ∑ k ≥ 0 ( n k ) x k y n = ∑ n ≥ 0 [ ( 1 + x ) y ] n = 1 1 − y − x y = 1 1 − y 1 1 − y 1 − y x = ∑ k ≥ 0 1 1 − y ( y 1 − y ) k x k \begin{aligned} A(x, y) &= \sum \limits_{k \ge 0} \left(\sum \limits_{n \ge 0}\binom{n}{k}y^n\right) x^k\\ &= \sum \limits_{n \ge 0}\sum \limits_{k \ge 0} \binom{n}{k} x^k y^n \\ &= \sum \limits_{n \ge 0} [(1 + x)y]^n \\ &= \dfrac{1}{1 - y - xy}\\ &= \dfrac{1}{1 - y} \dfrac{1}{1 - \dfrac{y}{1 - y}x}\\ &= \sum \limits_{k\ge 0}\dfrac{1}{1 - y}\left(\dfrac{y}{1 - y}\right)^kx^k\\ \end{aligned} A(x,y)=k≥0∑(n≥0∑(kn)yn)xk=n≥0∑k≥0∑(kn)xkyn=n≥0∑[(1+x)y]n=1−y−xy1=1−y11−1−yyx1=k≥0∑1−y1(1−yy)kxk
典例 Bobo String Count
题目大意
- 给定一个 01 串 t t t,求在长度为 n n n 且 t t t 恰好出现 k k k 次的 01 串个数,答案对 998244353 取模。
- 0 ≤ k ≤ n 0 \le k \le n 0≤k≤n, 1 ≤ n , ∣ t ∣ ≤ 5 × 1 0 4 1 \le n, |t| \le 5 \times 10^4 1≤n,∣t∣≤5×104。
解法
- 令
m
=
∣
t
∣
m = |t|
m=∣t∣,设
g
i
g_i
gi 表示长度为
i
+
m
i + m
i+m 的字符串长度为
m
m
m 的前后缀均为
t
t
t 的方案数,则:
- 若 i < m i < m i<m,若 m − i m - i m−i 是 t t t 的 border,则 g i g_i gi 为 1,否则为 0。
- 若 i ≥ m i \ge m i≥m,则 g i = 2 i − m g_i = 2^{i - m} gi=2i−m。
- 设
h
i
h_i
hi 表示长度为
i
+
m
i + m
i+m 的字符串长度为
m
m
m 的前后缀均为
t
t
t 且中间不再有
t
t
t 出现的方案数,则根据容斥原理有:
h i = g i − ∑ j = 1 i − 1 h j g i − j h_i = g_i - \sum\limits_{j = 1}^{i - 1}h_jg_{i - j} hi=gi−j=1∑i−1hjgi−j - 设
H
(
x
)
=
∑
i
≥
1
h
i
x
i
H(x) = \sum\limits_{i \ge 1}h_ix^i
H(x)=i≥1∑hixi,
G
(
x
)
=
∑
i
≥
0
g
i
x
i
G(x) = \sum\limits_{i\ge 0}g_ix^i
G(x)=i≥0∑gixi,则有:
G = H G + 1 ⇔ H = 1 − 1 G G = HG + 1 \Leftrightarrow H = 1 - \dfrac{1}{G} G=HG+1⇔H=1−G1 - 设
p
i
p_i
pi 表示长度为
i
+
m
i + m
i+m 的字符串前(后)缀为
t
t
t 的方案数,同样根据容斥原理有:
p i = 2 i − ∑ j = 1 i h j 2 i − j p_i = 2^i - \sum \limits_{j = 1}^{i}h_j2^{i-j} pi=2i−j=1∑ihj2i−j - 设
P
(
x
)
=
∑
i
≥
0
p
i
x
i
P(x) = \sum \limits_{i \ge 0}p_ix^i
P(x)=i≥0∑pixi,讨论
k
k
k:
- 若 k = 0 k = 0 k=0,则答案为 2 n − ∑ j = 0 n − m p j 2 n − m − j 2^n - \sum \limits_{j = 0}^{n- m}p_j2^{n - m - j} 2n−j=0∑n−mpj2n−m−j,时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn)。
- 若 k > 0 k > 0 k>0,则答案为 [ x n − m ] P 2 H k − 1 [x^{n - m}]P^2H^{k - 1} [xn−m]P2Hk−1,时间复杂度 O ( n log n log k ) \mathcal O(n \log n \log k) O(nlognlogk)。
EGF
-
即指数型生成函数 A ( x ) = ∑ i ≥ 0 a i i ! x i A(x) = \sum \limits_{i \ge 0}\frac{a_i}{i!}x^i A(x)=i≥0∑i!aixi。
-
若将 B B B 划分为若干个非空无序集合 A A A,则 B B B 与 A A A 的 E G F \mathbb{EGF} EGF 的关系为( A ( x ) A(x) A(x) 常数项必须为 0, B ( x ) B(x) B(x) 常数项必须为 1):
B ( x ) = ∑ i ≥ 0 A ( x ) i i ! = e A ( x ) , A ( x ) = ln B ( x ) B(x) = \sum \limits_{i \ge 0}\frac{A(x)^i}{i!} = e^{A(x)},A(x) = \ln B(x) B(x)=i≥0∑i!A(x)i=eA(x),A(x)=lnB(x)- 例如若
B
B
B 为排列,
A
A
A 为置换,则
B ( x ) = ∑ i ≥ 0 i ! i ! x i = ∑ i ≥ 0 x i = 1 1 − x A ( x ) = ∑ i ≥ 1 ( i − 1 ) ! i ! x i = ∑ i ≥ 1 x i i = ln 1 1 − x B(x) = \sum \limits_{i \ge 0} \dfrac{i!}{i!}x^i = \sum \limits_{i \ge 0}x^i = \dfrac{1}{1 - x} \\ A(x) = \sum \limits_{i \ge 1} \dfrac{(i - 1)!}{i!}x^i = \sum \limits_{i \ge 1}\dfrac{x^i}{i} = \ln \dfrac{1}{1 - x} \\ B(x)=i≥0∑i!i!xi=i≥0∑xi=1−x1A(x)=i≥1∑i!(i−1)!xi=i≥1∑ixi=ln1−x1 - 常见的例子即可通过 n n n 个点有标号无向图的 E G F \mathbb{EGF} EGF 取 ln \ln ln 得到 n n n 个点有标号无向连通图的 E G F \mathbb{EGF} EGF,对 n n n 个点有标号无向树的 E G F \mathbb{EGF} EGF 取 exp \text{exp} exp 即可得到 n n n 个点有标号无向森林的 E G F \mathbb{EGF} EGF。
- 例如若
B
B
B 为排列,
A
A
A 为置换,则
-
常用公式(即 Taylor \text{Taylor} Taylor 级数):
e x + e − x 2 = ∑ i ≥ 0 且 i 为偶数 x i i ! , e x − e − x 2 = ∑ i ≥ 0 且 i 为奇数 x i i ! ln ( 1 + x ) = ∑ i ≥ 1 ( − 1 ) i + 1 i x i , ln ( 1 − x ) = − ∑ i ≥ 1 x i i ( 1 + x ) α = 1 + ∑ i ≥ 1 ∏ j = α − i + 1 α j i ! x i \frac{e^x + e^{-x}}{2} = \sum \limits_{i \ge 0 且 i 为偶数} \frac{x^i}{i!},\ \frac{e^x-e^{-x}}{2} = \sum \limits_{i \ge 0 且 i 为奇数} \frac{x^i}{i!}\\ \ln (1 + x) = \sum\limits_{i\ge 1}\frac{(-1)^{i + 1}}{i}x^i, \ \ln(1 - x) = -\sum\limits_{i \ge 1}\frac{x^i}{i} \\ (1 + x)^{\alpha} = 1 + \sum \limits_{i \ge 1} \frac{\prod\limits_{j = \alpha - i +1}^{\alpha}j}{i!}x^i 2ex+e−x=i≥0且i为偶数∑i!xi, 2ex−e−x=i≥0且i为奇数∑i!xiln(1+x)=i≥1∑i(−1)i+1xi, ln(1−x)=−i≥1∑ixi(1+x)α=1+i≥1∑i!j=α−i+1∏αjxi
典例1 CF891E
题目大意
- 有 n n n 个数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an,要进行 k k k 次操作,每次随机选择一个数 x ∈ [ 1 , n ] x \in [1, n] x∈[1,n],把 a x a_x ax 减 1,将答案增加除 a x a_x ax 外所有数的乘积。
- 求最终答案的期望。
解法
- 设 k k k 次操作后 a i a_i ai 变成了 a i − b i a_i - b_i ai−bi,实际每次操作的贡献是操作前后序列乘积的差,即贡献序列是序列乘积的差分,因而总的贡献就是初始序列的乘积减去最终序列的乘积,即求 ∏ i = 1 n a i − ∏ i = 1 n ( a i − b i ) \prod \limits_{i = 1}^{n}a_i - \prod\limits_{i = 1}^{n}(a_i - b_i) i=1∏nai−i=1∏n(ai−bi) 的期望。
- 显然只要考虑后半部分,用 E G F \mathbb {EGF} EGF 推导所有方案的贡献之和,再除以总方案数即为期望。
F i ( x ) = ∑ j ≥ 0 a i − j j ! x j = ( a i − x ) e x F ( x ) = ∏ i = 1 n F i ( x ) = e n x ∏ i = 1 n ( a i − x ) F_i(x) = \sum\limits_{j \ge 0}\frac{a_i - j}{j!}x^j = (a_i - x)e^x \\ F(x) = \prod\limits_{i = 1}^{n}F_i(x) = e^{nx} \prod\limits_{i = 1}^{n}(a_i - x) Fi(x)=j≥0∑j!ai−jxj=(ai−x)exF(x)=i=1∏nFi(x)=enxi=1∏n(ai−x)
- F ( x ) F(x) F(x) 后半部分可以暴力卷积,同时我们只关心 [ x k ] F ( x ) [x^k]F(x) [xk]F(x), 将前半部分展开后直接计算即可。
典例2 有标号(弱连通)DAG计数
题目大意
- 对 n n n 个点的有标号(弱连通)DAG 计数。
- n ≤ 1 0 5 n \le 10^5 n≤105,答案对 998244353 取模。
解法
- 设
f
n
f_n
fn 表示
n
n
n 个点有标号 DAG 的个数,考虑枚举入度为 0 的点的个数
j
j
j 进行容斥,则这
j
j
j 个点可与剩下
i
−
j
i - j
i−j 个点任意连边,有:
f i = ∑ j = 1 i ( − 1 ) j + 1 ( i j ) 2 j ( i − j ) f i − j f_i = \sum \limits_{j = 1}^{i}(-1)^{j+1}\binom{i}{j}2^{j(i - j)}f_{i - j} fi=j=1∑i(−1)j+1(ji)2j(i−j)fi−j - 由
j
(
i
−
j
)
j(i - j)
j(i−j) 组合意义可知 (常用的变换技巧):
j ( i − j ) = ( i 2 ) − ( j 2 ) − ( i − j 2 ) j(i - j) = \binom{i}{2} - \binom{j}{2} - \binom{i - j}{2} j(i−j)=(2i)−(2j)−(2i−j) - 代入后得到:
f i = ∑ j = 1 i ( − 1 ) j + 1 i ! j ! ( i − j ) ! 2 ( i 2 ) 2 ( j 2 ) 2 ( i − j 2 ) f i − j f i i ! 2 ( i 2 ) = ∑ j = 1 i ( − 1 ) j + 1 j ! 2 ( j 2 ) f i − j ( i − j ) ! 2 ( i − j 2 ) \begin{aligned} f_i &= \sum \limits_{j = 1}^{i}(-1)^{j+1}\frac{i!}{j!(i -j)!}\frac{2^{\binom{i}{2}}}{2^{\binom{j}{2}}2^{\binom{i - j}{2}}}f_{i - j}\\ \frac{f_i}{i!2^{\binom{i}{2}}} &= \sum \limits_{j = 1}^{i}\frac{(-1)^{j + 1}}{j!2^{\binom{j}{2}}}\frac{f_{i - j}}{(i - j)!2^{\binom{i - j}{2}}} \\ \end{aligned} fii!2(2i)fi=j=1∑i(−1)j+1j!(i−j)!i!2(2j)2(2i−j)2(2i)fi−j=j=1∑ij!2(2j)(−1)j+1(i−j)!2(2i−j)fi−j - 设
F
(
x
)
=
∑
i
≥
0
f
i
i
!
2
(
i
2
)
x
i
F(x) = \sum \limits_{i \ge 0}\frac{f_i}{i!2^{\binom{i}{2}}}x^i
F(x)=i≥0∑i!2(2i)fixi,
G
(
x
)
=
∑
i
≥
1
(
−
1
)
i
+
1
i
!
2
(
i
2
)
x
i
G(x) = \sum \limits_{i\ge 1}\frac{(-1)^{i + 1}}{i!2^{\binom{i}{2}}}x^i
G(x)=i≥1∑i!2(2i)(−1)i+1xi,上式写为:
F = F G + 1 ⇔ F = 1 1 − G F = FG + 1 \Leftrightarrow F = \frac{1}{1 - G} F=FG+1⇔F=1−G1 - 再设
h
n
h_n
hn 表示
n
n
n 个点有标号弱连通 DAG 的个数,
P
(
x
)
=
∑
i
≥
0
f
i
i
!
x
i
,
Q
(
x
)
=
∑
i
≥
0
h
i
i
!
x
i
P(x) = \sum \limits_{i \ge 0}\frac{f_i}{i!}x^i, Q(x) = \sum \limits_{i \ge 0}\frac{h_i}{i!}x^i
P(x)=i≥0∑i!fixi,Q(x)=i≥0∑i!hixi,由多项式
exp
\exp
exp 的意义可知:
Q ( x ) = ln P ( x ) Q(x) = \ln P(x) Q(x)=lnP(x) - 总时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
常见技巧
-
将生成函数 A ( x ) A(x) A(x) 乘上 1 1 − x \frac{1}{1-x} 1−x1 相当于乘上 ∑ i ≥ 0 x i \sum \limits_{i\ge0}x^i i≥0∑xi, 实际上就是对 A ( x ) A(x) A(x) 的系数求前缀和,乘 ( 1 1 − x ) k (\frac{1}{1-x})^k (1−x1)k 即求 k k k 次前缀和,参见组合数学高阶前缀和部分。
-
欲求 ∏ j = 1 m ( 1 − x c j ) \prod\limits_{j = 1}^{m}(1 - x^{c_j}) j=1∏m(1−xcj),其中 ∑ j = 1 m c i = n \sum \limits_{j = 1}^{m}c_i = n j=1∑mci=n,通常有两种处理方法:
-
分治 NTT,时间复杂度 O ( n log n log m ) \mathcal O(n \log n \log m) O(nlognlogm)。
-
根据 Taylor 级数,预处理 cnt i \text{cnt}_i cnti 表示数组 c c c 中 i i i 的出现次数,
∏ j = 1 m ( 1 − x c j ) = exp ∑ j = 1 m ln ( 1 − x c j ) = exp ( − ∑ j = 1 m ∑ i ≥ 1 x c j i i ) = exp ( − ∑ i = 1 n ∑ i ∣ j i cnt i x j j ) \begin{aligned} \prod\limits_{j = 1}^{m}(1 - x^{c_j}) &= \exp \sum \limits_{j = 1}^{m}\ln(1 - x ^{c_j}) \\ &= \exp \left(-\sum \limits_{j = 1}^{m} \sum \limits_{i \ge 1} \dfrac{x^{c_ji}}{i}\right)\\ &= \exp \left(-\sum \limits_{i = 1}^{n}\sum \limits_{i|j}\frac{i\text{cnt}_ix^{j}}{j}\right)\\ \end{aligned} j=1∏m(1−xcj)=expj=1∑mln(1−xcj)=exp(−j=1∑mi≥1∑ixcji)=exp −i=1∑ni∣j∑jicntixj
时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
-
集合幂级数
- 类比数列的生成函数,设全集
U
=
{
1
,
2
,
…
,
n
}
U = \{1,2,\dots,n\}
U={1,2,…,n},定义集合幂级数:
f = ∑ S ∈ 2 U f S x S f = \sum\limits_{S\in 2^U}f_S x^S f=S∈2U∑fSxS - 加减法定义为系数相加减,乘法定义为:
h = f ∗ g ⇔ ∑ S ∈ 2 U h S x S = ( ∑ S ∈ 2 U f S x S ) ( ∑ S ∈ 2 U g S x S ) = ∑ S ∈ 2 U ( ∑ L ∈ 2 U ∑ R ∈ 2 U [ L ∗ R = S ] f L g R ) x S h = f * g \Leftrightarrow \sum \limits_{S \in 2^U}h_Sx^S = \left(\sum\limits_{S \in 2^U}f_Sx^S\right)\left(\sum\limits_{S \in 2^U}g_Sx^S\right) = \sum\limits_{S\in 2^U}\left(\sum \limits_{L\in 2^U}\sum\limits_{R\in 2^U}[L*R=S] f_Lg_R\right)x^S h=f∗g⇔S∈2U∑hSxS=(S∈2U∑fSxS)(S∈2U∑gSxS)=S∈2U∑(L∈2U∑R∈2U∑[L∗R=S]fLgR)xS - 当
*
取不同的运算时会产生不同的效果。
OR 卷积
-
取
*
为或运算,则有 h S = ∑ L ∈ 2 U ∑ R ∈ 2 U [ L ∪ R = S ] f L g R h_S = \sum \limits_{L\in 2^U}\sum\limits_{R\in 2^U}[L\cup R=S]f_Lg_R hS=L∈2U∑R∈2U∑[L∪R=S]fLgR。 -
定义 f f f 的 莫比乌斯变换 为集合幂级数 f ^ \hat{f} f^,其中 f S ^ = ∑ T ⊆ S f T \hat{f_S} = \sum\limits_{T \subseteq S}f_T fS^=T⊆S∑fT,则 h S ^ = f S ^ g S ^ \hat{h_S} = \hat{f_S}\hat{g_S} hS^=fS^gS^。
-
由容斥原理,定义 f ^ \hat{f} f^ 的 莫比乌斯反演 为 f f f,其中 f S = ∑ T ⊆ S ( − 1 ) ∣ S ∣ − ∣ T ∣ f T ^ f_S = \sum\limits_{T \subseteq S}(-1)^{|S| - |T|}\hat{f_T} fS=T⊆S∑(−1)∣S∣−∣T∣fT^。
-
可将莫比乌斯变换视为高维前缀和,将莫比乌斯反演视为其逆过程,卷积只需要在对应位置相乘即可,时间复杂度 O ( n 2 n ) \mathcal O(n2^n) O(n2n)。
AND 卷积
- 取
*
为与运算,同 OR 卷积的区别仅在于 0/1 位置互换,其余过程完全一致。
XOR 卷积
- 取
*
为异或运算,则有 h S = ∑ L ∈ 2 U ∑ R ∈ 2 U [ L ⊕ R = S ] f L g R h_S = \sum \limits_{L\in 2^U}\sum\limits_{R\in 2^U}[L \oplus R=S]f_Lg_R hS=L∈2U∑R∈2U∑[L⊕R=S]fLgR。 - 注意到 1 2 n ∑ T ∈ 2 U ( − 1 ) ∣ T ∩ S ∣ = [ S = ∅ ] \dfrac{1}{2^n} \sum \limits_{T\in 2^U}(-1)^{|T\cap S|}=[S = \varnothing] 2n1T∈2U∑(−1)∣T∩S∣=[S=∅],证明即考虑 S S S 不为空时,任取 v ∈ S v \in S v∈S, T T T 和 T ⊕ v T \oplus v T⊕v 产生贡献的和恒为 0。
- 因而可化简得到:
h S = ∑ L ∈ 2 U ∑ R ∈ 2 U [ L ⊕ R ⊕ S = ∅ ] f L g R = ∑ L ∈ 2 U ∑ R ∈ 2 U 1 2 n ∑ T ∈ 2 U ( − 1 ) ∣ T ∩ ( L ⊕ R ⊕ S ) ∣ f L g R = ∑ L ∈ 2 U ∑ R ∈ 2 U 1 2 n ∑ T ∈ 2 U ( − 1 ) ∣ T ∩ L ∣ ( − 1 ) ∣ T ∩ R ∣ ( − 1 ) ∣ T ∩ S ∣ f L g R = 1 2 n ∑ T ∈ 2 U ( − 1 ) ∣ T ∩ S ∣ ( ∑ L ∈ 2 U ( − 1 ) ∣ T ∩ L ∣ f L ) ( ∑ R ∈ 2 U ( − 1 ) ∣ T ∩ R ∣ g R ) \begin{aligned} h_S &= \sum \limits_{L \in 2^U}\sum\limits_{R\in 2^U}[L\oplus R\oplus S=\varnothing]f_Lg_R \\ &= \sum \limits_{L \in 2^U}\sum\limits_{R\in 2^U}\dfrac{1}{2^n}\sum \limits_{T\in 2^U}(-1)^{|T\cap(L\oplus R\oplus S)|}f_Lg_R \\ &= \sum \limits_{L \in 2^U}\sum\limits_{R\in 2^U}\dfrac{1}{2^n}\sum \limits_{T\in 2^U}(-1)^{|T\cap L|}(-1)^{|T\cap R|}(-1)^{|T\cap S|}f_Lg_R \\ &= \dfrac{1}{2^n}\sum\limits_{T\in 2^U}(-1)^{|T\cap S|}\left(\sum\limits_{L\in 2^U}(-1)^{|T\cap L|}f_L\right)\left(\sum\limits_{R\in 2^U}(-1)^{|T\cap R|}g_R\right)\\ \end{aligned} hS=L∈2U∑R∈2U∑[L⊕R⊕S=∅]fLgR=L∈2U∑R∈2U∑2n1T∈2U∑(−1)∣T∩(L⊕R⊕S)∣fLgR=L∈2U∑R∈2U∑2n1T∈2U∑(−1)∣T∩L∣(−1)∣T∩R∣(−1)∣T∩S∣fLgR=2n1T∈2U∑(−1)∣T∩S∣(L∈2U∑(−1)∣T∩L∣fL)(R∈2U∑(−1)∣T∩R∣gR) - 根据上述结果(可取
g
=
x
∅
g = x^{\varnothing}
g=x∅)定义
f
f
f 的 沃尔什变换 为集合幂级数
f
^
\hat{f}
f^,
f
^
\hat{f}
f^ 的 沃尔什逆变换 为
f
f
f,其中:
f S ^ = ∑ T ∈ 2 U ( − 1 ) ∣ S ∩ T ∣ f T ⇔ f S = 1 2 n ∑ T ∈ 2 U ( − 1 ) ∣ S ∩ T ∣ f T ^ \hat{f_S} = \sum\limits_{T\in 2^U}(-1)^{|S\cap T|}f_T \Leftrightarrow f_S = \dfrac{1}{2^n}\sum\limits_{T\in 2^U}(-1)^{|S\cap T|}\hat{f_T} fS^=T∈2U∑(−1)∣S∩T∣fT⇔fS=2n1T∈2U∑(−1)∣S∩T∣fT^ - 此时便满足 h s ^ = f s ^ g s ^ \hat{h_s} = \hat{f_s} \hat{g_s} hs^=fs^gs^。
- 考虑怎样快速求一个集合幂级数 f f f 的沃尔什变换,沃尔什逆变换只需乘上 1 2 n \dfrac{1}{2^n} 2n1 的系数即可。
- 设
f
S
^
(
i
)
=
∑
S
⊕
T
⊆
{
1
,
2
,
…
,
i
}
(
−
1
)
∣
S
∩
T
∩
{
1
,
2
,
…
,
i
}
∣
\hat{f_S}^{(i)} = \sum \limits_{S\oplus T\subseteq\{1,2,\dots,i\}}(-1)^{|S\cap T \cap\{1,2,\dots,i\}|}
fS^(i)=S⊕T⊆{1,2,…,i}∑(−1)∣S∩T∩{1,2,…,i}∣,则有
f
S
^
(
0
)
=
f
S
,
f
S
^
(
n
)
=
f
S
^
\hat{f_S}^{(0)} = f_S,\hat{f_S}^{(n)} = \hat{f_S}
fS^(0)=fS,fS^(n)=fS^,不难得到:
f S ^ ( i ) = f S ^ ( i − 1 ) + f ^ S ∪ { i } ( i − 1 ) f ^ S ∪ { i } ( i ) = f S ^ ( i − 1 ) − f ^ S ∪ { i } ( i − 1 ) \hat{f_S}^{(i)} = \hat{f_S}^{(i-1)}+\hat{f}_{S\cup\{i\}}^{(i - 1)}\ \ \hat{f}_{S\cup\{i\}}^{(i)} = \hat{f_S}^{(i-1)}-\hat{f}_{S\cup\{i\}}^{(i - 1)} fS^(i)=fS^(i−1)+f^S∪{i}(i−1) f^S∪{i}(i)=fS^(i−1)−f^S∪{i}(i−1) - 顺序枚举 i i i 从 1 到 n n n 迭代即可,时间复杂度 O ( n 2 n ) \mathcal O(n2^n) O(n2n)。
子集卷积
- 定义
h
=
f
⋅
g
h = f · g
h=f⋅g 表示
f
f
f 和
g
g
g 的子集卷积,其中
h
h
h 为集合幂级数:
h S = ∑ L ∈ 2 U ∑ R ∈ 2 U [ L ∪ R = S ] [ L ∩ R = ∅ ] f L g R h_S = \sum \limits_{L\in 2^U}\sum\limits_{R\in 2^U}[L \cup R = S][L \cap R = \varnothing] f_Lg_R hS=L∈2U∑R∈2U∑[L∪R=S][L∩R=∅]fLgR - 注意到
[
L
∪
R
=
S
]
[
L
∩
R
=
∅
]
=
[
L
∪
R
=
S
]
[
∣
L
∣
+
∣
R
∣
=
∣
S
∣
]
[L \cup R = S][L \cap R = \varnothing] = [L\cup R = S][|L| + |R| = |S|]
[L∪R=S][L∩R=∅]=[L∪R=S][∣L∣+∣R∣=∣S∣],可设集合占位幂级数(其中
z
a
∗
z
b
=
z
a
+
b
z^a * z^b = z^{a + b}
za∗zb=za+b):
σ = ∑ S ∈ 2 U f S z ∣ S ∣ x S \sigma = \sum \limits_{S\in 2^U}f_Sz^{|S|}x^{S} σ=S∈2U∑fSz∣S∣xS - 对集合幂级数做 OR 卷积时用快速莫比乌斯变换优化,对 z z z 做卷积时暴力即可,时间复杂度 O ( n 2 2 n ) \mathcal O(n^22^n) O(n22n)。
- 也可将上述四种卷积变换的结果理解成点值,做多项式运算时只需在最外层变换和反演(逆变换)一次。
const int mod = 1e9 + 9;
const int inv2 = mod + 1 >> 1;
inline void orFMT(int *f, int n, int opt)
{
int s = 1 << n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < s; ++j)
if (!(j >> i & 1))
opt == 1 ? add(f[1 << i | j], f[j]) : dec(f[1 << i | j], f[j]);
}
inline void andFMT(int *f, int n, int opt)
{
int s = 1 << n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < s; ++j)
if (!(j >> i & 1))
opt == 1 ? add(f[j], f[1 << i | j]) : dec(f[j], f[1 << i | j]);
}
inline void FWT(int *f, int n, int opt)
{
int s = 1 << n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < s; ++j)
if (!(j >> i & 1))
{
int tj = 1 << i | j;
int l = f[j],
r = f[tj];
f[j] = f[tj] = l;
add(f[j], r);
dec(f[tj], r);
}
if (opt == -1)
{
int inv_s = 1;
for (int i = 0; i < n; ++i)
inv_s = 1ll * inv2 * inv_s % mod;
for (int i = 0; i < s; ++i)
f[i] = 1ll * f[i] * inv_s % mod;
}
}
// 以下函数传入后 f,g 内的信息都将改变,结果存入 f
inline void polyBit(void (*trans)(int*, int, int), int *f, int *g, int n)
{ // And Or Xor 卷积
trans(f, n, 1);
trans(g, n, 1);
int s = 1 << n;
for (int i = 0; i < s; ++i)
f[i] = 1ll * f[i] * g[i] % mod;
trans(f, n, -1);
}
inline void polySubset(int *f, int *g, int n)
{
static int tf[N][S], tg[N][S], th[N][S], cnt[S];
int s = 1 << n;
for (int i = 1; i < s; ++i)
cnt[i] = cnt[i ^ (i & -i)] + 1;
for (int i = 0; i < s; ++i)
tf[cnt[i]][i] = f[i], tg[cnt[i]][i] = g[i];
for (int i = 0; i <= n; ++i)
orFMT(tf[i], n, 1), orFMT(tg[i], n, 1);
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= i; ++j)
for (int k = 0; k < s; ++k)
th[i][k] = (1ll * tf[j][k] * tg[i - j][k] + th[i][k]) % mod;
for (int i = 0; i <= n; ++i)
orFMT(th[i], n, -1);
for (int i = 0; i < s; ++i)
f[i] = th[cnt[i]][i];
for (int i = 0; i < s; ++i)
cnt[i] = 0;
for (int i = 0; i <= n; ++i)
for (int j = 0; j < s; ++j)
tf[i][j] = tg[i][j] = th[i][j] = 0;
}