原文:https://noshi91.hatenablog.com/entry/2024/03/16/224034

Mk,i=[xi]g(x)k M_{k,i}=[x^i]g(x)^k

幂投影给 f(0)=0f(0)=0 和向量 aa,计算

bk=iai[xi]f(x)k b_k=\sum_i a_i\,[x^i]f(x)^k

也就是 b=Mab=Ma。而复合

[xi]f(g(x))=kfkMk,i [x^i]f(g(x))=\sum_k f_k M_{k,i}

也就是 c=MTfc=M^\mathsf T f。转置原理保证,只要能 O(nlog2n)O(n\log^2 n) 算幂投影,复合也能在同样复杂度内完成,额外开销只有 O(n)O(n)

所以先看幂投影。构造

a^(x)=i=0n1aixn1i \hat a(x)=\sum_{i=0}^{n-1} a_i x^{n-1-i}

对固定的 kk,有

[xn1](a^(x)f(x)k)=bk [x^{n-1}]\bigl(\hat a(x)f(x)^k\bigr)=b_k

kkyy 打包:

k0bkyk=[xn1]a^(x)1yf(x) \sum_{k\ge0} b_k y^k = [x^{n-1}]\frac{\hat a(x)}{1-y f(x)}

于是问题变成:求

[xN]P(x,y)Q(x,y)modyn [x^N]\frac{P(x,y)}{Q(x,y)}\bmod y^n

初始时 P=a^P=\hat aQ=1yfQ=1-y fN=n1N=n-1

这可以用 Bostan–Mori 的二元版本。一元时是分子分母同乘 Q(x)Q(-x),让分母变成 xx 的偶函数,再按 NN 的奇偶折半。二元也一样:

PQ=P(x,y)Q(x,y)Q(x,y)Q(x,y) \frac{P}{Q} = \frac{P(x,y)Q(-x,y)}{Q(x,y)Q(-x,y)}

V(x2,y)=Q(x,y)Q(x,y) V(x^2,y)=Q(x,y)Q(-x,y)

并把分子按 xx 的奇偶拆开:

P(x,y)Q(x,y)=Te(x2,y)+xTo(x2,y) P(x,y)Q(-x,y) = T_e(x^2,y)+xT_o(x^2,y)

偶次项只来自 TeT_e,奇次项只来自 ToT_o

[x2t]PQ=[xt]TeV [x^{2t}]\frac{P}{Q} = [x^t]\frac{T_e}{V} [x2t+1]PQ=[xt]ToV [x^{2t+1}]\frac{P}{Q} = [x^t]\frac{T_o}{V}

每层根据 NN 的奇偶选 TeT_eToT_o,然后令

PTσ,QV,NN2 P\leftarrow T_\sigma,\quad Q\leftarrow V,\quad N\leftarrow\left\lfloor\frac N2\right\rfloor

N=0N=0 时,答案就是

P(0,y)Q(0,y) \frac{P(0,y)}{Q(0,y)}

做一次一元幂级数求逆即可。

注意每层 xx 的次数减半,yy 的次数翻倍。由于只需要 [xN][x^N]P,QP,Q 都可以截到 xxNN 次以内,所以第 jjxx 大小是 O(n/2j)O(n/2^j)yy 大小是 O(2j)O(2^j),二元规模一直是 O(n)O(n)。总层数 O(logn)O(\log n),每层一次 O(nlogn)O(n\log n) 的二维卷积,总复杂度 O(nlog2n)O(n\log^2 n)

复合这边就是转置。正向算法里每一步都是线性映射:反转 aa 得到 a^\hat a;乘 Q(x,y)Q(-x,y) 并截断;按奇偶抽取压缩;最后乘 Q(0,y)1Q(0,y)^{-1}。转置时把这些步骤倒过来,并逐项转置:先和 Q(0,y)1Q(0,y)^{-1} 做中积,然后把上一层的 PP2i+σ2i+\sigma 摊开,乘法转置成中积,最后反转。QQ 链不依赖输入,可以先正向算一遍存下 QjQ_jσj\sigma_j,再倒序处理 PP 链。

转置乘法就是相关。正向 w=uqw=u*q 截断,转置是

uˉ[i]=kwˉ[k]q[ki] \bar u[i]=\sum_k \bar w[k]q[k-i]

qq 反转后,相当于取乘积的一段中间窗口。二维时对两个下标同样处理。代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// out[y][i] = sum_{jy,jx} W[jy][jx] * Qb[jy-y][jx-i]
static poly2 tmul2d(const poly2& W, const poly2& Qb, int oy, int ox) {
int wy = W.size(), wx = W[0].size(), qy = Qb.size(), qx = Qb[0].size();
int S = wx + qx - 1, need = wy * S, L = 1;
while (L < need) L <<= 1;
poly a(L, mint(0)), b(L, mint(0));
for (int j = 0; j < wy; j++)
for (int i = 0; i < wx; i++)
a[j * S + i] = W[j][i];
for (int j = 0; j < qy; j++)
for (int i = 0; i < qx; i++)
b[j * S + i] = Qb[qy - 1 - j][qx - 1 - i];
ntt(a, 0);
ntt(b, 0);
for (int i = 0; i < L; i++) a[i] *= b[i];
ntt(a, 1);
poly2 R(oy, poly(ox));
for (int y = 0; y < oy; y++)
for (int i = 0; i < ox; i++) {
long long d = (long long)(qy - 1 + y) * S + (qx - 1 + i);
if (d < L) R[y][i] = a[d];
}
return R;
}

复合逆不用转置版本,直接调一次正向幂投影。设 f(g(x))=xf(g(x))=x,取 a=en1a=e_{n-1},得到

wk=[xn1]f(x)k w_k=[x^{n-1}]f(x)^k

用留数换元 x=g(t)x=g(t)

kwkyk=Resxxn1yf(x)=Restg(t)ng(t)1yt \sum_k w_k y^k = \operatorname{Res}_x\frac{x^{-n}}{1-y f(x)} = \operatorname{Res}_t\frac{g(t)^{-n}g'(t)}{1-y t}

于是

wj=[t1j](gng) w_j=[t^{-1-j}]\bigl(g^{-n}g'\bigr)

再利用

gng=11n(g1n) g^{-n}g'=\frac{1}{1-n}\left(g^{1-n}\right)'

[t1j]H=j[tj]H [t^{-1-j}]H'=-j\,[t^{-j}]H

得到

wj=jn1[tj]g(t)1n w_j=\frac{j}{n-1}\,[t^{-j}]g(t)^{1-n}

m=n1m=n-1g=tGg=tG,则

[ti]Gm=mmiwmi,0im1 [t^i]G^{-m} = \frac{m}{m-i}w_{m-i},\qquad 0\le i\le m-1

有了 GmG^{-m} 的系数,取 1/m-1/m 次幂得到 GG,再乘 tt 就是 gg。实现上只是一次幂投影加一次 exp/log。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
poly comp_inverse(poly f, int n) {
if (n <= 1) return poly(n);
f.resize(n);
f[0] = mint(0);
poly a(n);
a[n - 1] = mint(1);
poly w = power_projection(a, f, n);
int m = n - 1;
poly R(m);
for (int i = 0; i < m; i++)
R[i] = w[m - i] * mint(m) * mint(m - i).inv();
mint iR0 = R[0].inv();
for (int i = 0; i < m; i++) R[i] *= iR0;
poly lg = log_series(R, m);
mint c = -mint(m).inv();
for (auto& x : lg) x *= c;
poly Gp = exp_series(lg, m);
mint g1 = f[1].inv();
poly g(n);
for (int i = 0; i < m; i++) g[i + 1] = Gp[i] * g1;
return g;
}