拉格朗日插值

给出 nn 个点 (xi,yi)(x_i,y_i),求出过这 nn 个点的 n1n-1 次多项式 L(x)L(x)

拉格朗日插值多项式:

L(x)=i=0nji(xxj)ji(xixj)yiL(x)=\sum_{i=0}^n\frac{\prod_{j\ne i}(x-x_j)}{\prod_{j\ne i}(x_i-x_j)}\cdot y_i

时间复杂度 O(n2)O(n^2)


快速傅里叶变换 FFT

单位根

定义:ωn=e2πin=cos(2πn)+isin(2πn)\omega_n=e^{\frac{2\pi i}n}=\cos(\frac{2\pi}n)+i\cdot\sin(\frac{2\pi}n)

两个复数相乘,模长相乘,幅角相加。

性质:

  1. ω2nn+k=ω2nk\omega_{2n}^{n+k}=-\omega_{2n}^k
  2. ω2n2n+k=ω2nk\omega_{2n}^{2n+k}=\omega_{2n}^k
  3. ωmnmk=ωnk\omega_{mn}^{mk}=\omega_n^k

点值序列

一个 nn 次多项式可以由 n+1n+1 个点确定。

而将 f×gf\times g 可以由 ff2n+12n+1 个点和 gg2n+12n+1 个点对应相乘得到。

快速系数转点值 DFT

f(ωnk)=a0+a1ωnk+a2ωn2k++an1ωn(n1)k=a0+a2ωn2k++an2ωn(n2)k+a1ωnk+a3ωn3k++an1ωn(n1)k=a0+a2ωn/22k++an2ωn/2(n2)k+ωnk(a1+a3ωn/22k++an1ωn/2(n2)k)\begin{aligned} f\left(\omega_n^k\right)&=a_0+a_1\omega_n^k+a_2\omega_n^{2k}+\cdots+a_{n-1}\omega_n^{(n-1)k}\\ &=a_0+a_2\omega_n^{2k}+\cdots+a_{n-2}\omega_n^{(n-2)k}+a_1\omega_n^k+a_3\omega_n^{3k}+\cdots+a_{n-1}\omega_n^{(n-1)k}\\ &=a_0+a_2\omega_{n/2}^{2k}+\cdots+a_{n-2}\omega_{n/2}^{(n-2)k}+\omega_n^k\left(a_1+a_3\omega_{n/2}^{2k}+\cdots+a_{n-1}\omega_{n/2}^{(n-2)k}\right)\\ \end{aligned}

故可以拆成奇偶两部分,偶在左,奇在右。将两部分分别处理完后,可以合并为:

{f(ωnk)=g0(ωn/2k)+ωnkg1(ωn/2k)f(ωnk+(n/2))=g0(ωn/2k)ωnkg1(ωn/2k)\begin{cases} f\left(\omega_n^k\right)&=g_0\left(\omega_{n/2}^k\right)+\omega_n^k\cdot g_1\left(\omega_{n/2}^k\right)\\ f\left(\omega_n^{k+(n/2)}\right)&=g_0\left(\omega_{n/2}^k\right)-\omega_n^k\cdot g_1\left(\omega_{n/2}^k\right) \end{cases}

第二个式子的符号是 +(n/2)+(n/2) 来的。

快速点值转系数 IDFT

将上式 ωnk\omega_n^k 前的符号取反一下即可。最后记得再 ÷n\div n

非递归版本

考虑位逆序置换(蝴蝶变换),0,1,2,3,4,5,6,70,1,2,3,4,5,6,7 会变换成 0,4,2,6,1,5,3,70,4,2,6,1,5,3,7。即翻转 ii 的二进制表示后即为最终位置的下标。

定义 c(i)c(i) 表示 ii 的翻转二进制表示。它等于 c(i2)c\left(\lfloor\frac i2\rfloor\right) 右移一位后补上最高位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef double db;
typedef std::complex<db> comp;
const db PI = acos(-1.0);
const int N = 3e6 + 7;
void DFT(comp* f, int n, int rev) { // 1: 正变换,-1: 逆变换
static int c[N];
static comp tmp[N];
for (int i = 0; i < n; i++) tmp[i] = f[i];
for (int i = 0; i < n; i++) f[i] = tmp[c[i] = (c[i / 2] / 2) | ((i & 1) ? (n / 2) : 0)];
for (int l = 2; l <= n; l *= 2) {
comp step(cos(2 * PI / l), sin(2 * PI / l * rev));
for (int i = 0; i + l <= n; i += l) {
comp cur(1, 0);
for (int k = i; k < i + l / 2; k++, cur *= step)
tmp[k] = f[k] + cur * f[k + l / 2], tmp[k + l / 2] = f[k] - cur * f[k + l / 2];
}
for (int i = 0; i < n; i++) f[i] = tmp[i];
}
}

快速数论变换 NTT [UNFINISHED]

原根有着和单位根相似的性质。要求模数形如 q×2k+1q\times2^k+1NTT 模数表


快速莫比乌斯变换 FMT

ci=j and k=iajbkc_i=\sum\limits_{j~\text{and}~k=i}a_jb_k

对于集合幂级数 ff,定义 ff'fS=STfTf'_S=\sum_{S\subseteq T}f_T。即 SS 所有超集之和。等价于高维后缀和。

于是上式变为了:

cS=STPQ=TaPbQ=SPaPSQbQ=apbQ\begin{aligned} c'_S&=\sum_{S\subseteq T}\sum_{P\cap Q=T}a_P\cdot b_Q\\ &=\sum_{S\subseteq P}a_P\sum_{S\subseteq Q}b_Q\\ &=a'_p\cdot b'_Q \end{aligned}

求出 cSc'_S 后,高维差分得到 cSc_S

ci=j or k=iajbkc_i=\sum\limits_{j~\text{or}~k=i}a_jb_k,把后缀和改为前缀和即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Or(int n, LL* a, bool rev) {  // 0: 正变换,1: 逆变换
for (int i = 0; i < n; i++)
for (int j = 0; j < (1 << n); j++)
if (j & (1 << i)) {
a[j] += (rev ? mod - a[j - (1 << i)] : a[j - (1 << i)]);
if (a[j] >= mod) a[j] -= mod;
}
}
void And(int n, LL* a, bool rev) { // 0: 正变换,1: 逆变换
for (int i = 0; i < n; i++)
for (int j = (1 << n) - 1; ~j; j--)
if (!(j & (1 << i))) {
a[j] += (rev ? mod - a[j | (1 << i)] : a[j | (1 << i)]);
if (a[j] >= mod) a[j] -= mod;
}
}

快速沃尔什变换 FWT

ci=j xor k=iajbkc_i=\sum\limits_{j~\text{xor}~k=i}a_jb_k

对称差:AΔB={xxAB,x∉AB}A\Delta B=\{x|x\in A\cup B,x\not\in A\cap B\}

对于集合幂级数 ff,定义 ff'fS=TfT(1)STf'_S=\sum_Tf_T\cdot(-1)^{|S\cap T|}

推一推:

cS=T(1)STPΔQ=SaPbQ=PaPQbQ(1)(PΔQ)T=PaP(1)PTQbQ(1)QT=aPbQ\begin{aligned} c'_S&=\sum_T(-1)^{|S\cap T|}\sum_{P\Delta Q=S}a_P\cdot b_Q\\ &=\sum_Pa_P\sum_Qb_Q\cdot(-1)^{|(P\Delta Q)\cap T|}\\ &=\sum_Pa_P\cdot(-1)^{|P\cap T|}\sum_Qb_Q\cdot(-1)^{|Q\cap T|}\\ &=a'_P\cdot b'_Q \end{aligned}

问题变成了如何求 aSa'_SbSb'_S

类似 FFT 地,我们按照是否含有第 n1n-1 个元素,将所有下标分为 02n110\sim 2^{n-1}-12n12n12^{n-1}\sim2^n-1。假设左部递归出来为 g0,S=0T<2n1fT(1)STg_{0,S}=\sum_{0\le T<2^{n-1}}f_T\cdot(-1)^{|S\cap T|},右部递归出来为 g1,S=2n1T<2nfT(1)ST1g_{1,S}=\sum_{2^{n-1}\le T<2^n}f_T\cdot(-1)^{|S\cap T|-1}1-1 是因为去掉了第 n1n-1 个元素。

推一下左部的表达式:

f0,S=0T<2nfT(1)ST=0T<2n1fT(1)ST+2n1T<2nfT(1)ST=0T<2n1fT(1)ST+2n1T<2nfT(1)(S{n+1})T1=g0,S+g1,S\begin{aligned} f'_{0,S}&=\sum_{0\le T<2^n}f_T\cdot(-1)^{|S\cap T|}\\ &=\sum_{0\le T<2^{n-1}}f_T\cdot(-1)^{|S\cap T|}+\sum_{2^{n-1}\le T<2^n}f_T\cdot(-1)^{|S\cap T|}\\ &=\sum_{0\le T<2^{n-1}}f_T\cdot(-1)^{|S\cap T|}+\sum_{2^{n-1}\le T<2^n}f_T\cdot(-1)^{|(S\cup\{n+1\})\cap T|-1}\\ &=g_{0,S}+g_{1,S} \end{aligned}

同理,右部的表达式:

f1,S{n1}=0T<2nfT(1)(S{n+1})T=0T<2n1fT(1)ST+2n1T<2nfT(1)(S{n+1})T=g0,Sg1,S\begin{aligned} f'_{1,S\cup\{n-1\}}&=\sum_{0\le T<2^n}f_T\cdot(-1)^{|(S\cup\{n+1\})\cap T|}\\ &=\sum_{0\le T<2^{n-1}}f_T\cdot(-1)^{|S\cap T|}+\sum_{2^{n-1}\le T<2^n}f_T\cdot(-1)^{|(S\cup\{n+1\})\cap T|}\\ &=g_{0,S}-g_{1,S} \end{aligned}

逆变换记得每层 ÷2\div2

1
2
3
4
5
6
7
8
9
10
11
void Xor(int n, LL* f, bool rev) {  // 0: 正变换,1: 逆变换
for (int l = 2; l <= (1 << n); l *= 2)
for (int i = 0; i < (1 << n); i += l)
for (int k = 0; k < l / 2; k++) {
LL u = f[i + k], v = f[i + k + l / 2];
f[i + k] = u + v, f[i + k + l / 2] = u - v + mod;
if (f[i + k] >= mod) f[i + k] -= mod;
if (f[i + k + l / 2] >= mod) f[i + k + l / 2] -= mod;
if (rev) (f[i + k] *= inv2) %= mod, (f[i + k + l / 2] *= inv2) %= mod;
}
}