, the compositional inverse (reversion) with , power projection , Lagrange inversion, and the Taylor shift with its applications (sample shifting, for all ). Uses the core of NTT and Power Series, and shiftSamples from Evaluation and Interpolation.

OperationMethodCost
Brent–Kung baby-step giant-step, fine for
Kinoshita–Li (transposed power projection) ( in about 2 s)
power projection , Graeffe / Bostan–Mori in two variables on
reversion Newton with composition with Brent–Kung
reversionpower projection + Lagrange inversion + -th root
single coefficient of an inverseLagrange: one power,
one convolution
from sample shift (convolution)
for all Taylor shift by 1 of
Poly compose(const Poly& f, const Poly& g, int n) {        // f(g(x)) mod x^n, any g (Brent-Kung)
    Poly res(n, 0); if (f.empty() || n == 0) return res;
    int d = sz(f), k = max(1, (int)sqrt((double)d)); Poly gm = polyTrunc(g, n);
    vector<Poly> pw(k + 1); pw[0] = Poly(n, 0); pw[0][0] = 1;          // g^0 .. g^k mod x^n
    for (int i = 1; i <= k; i++) pw[i] = mulTrunc(pw[i - 1], gm, n);
    int blocks = (d + k - 1) / k;
    for (int j = blocks - 1; j >= 0; j--) {                 // Horner in g^k: res = res g^k + F_j(g)
        if (j != blocks - 1) res = mulTrunc(res, pw[k], n);
        for (int i = 0; i < k && j * k + i < d; i++) { mint c = f[j * k + i]; if (c == mint(0)) continue; for (int t = 0; t < n; t++) res[t] += c * pw[i][t]; }
    }
    return res;
}
Poly compositionalInverse(const Poly& g, int n) {           // g(0) = 0, g'(0) != 0; Newton, O(n^2)
    Poly h = {0, g[1].inv()}, dg = derivative(g);
    for (int len = 2; len < n;) {
        len = min(2 * len, n);
        Poly gh = compose(g, h, len); gh[1] -= 1;           // g(h) - x
        Poly step = mulTrunc(gh, polyInv(compose(dg, h, len), len), len);
        h.resize(len); for (int i = 0; i < len; i++) h[i] -= step[i];
    }
    h.resize(n); return h;
}
Poly taylorShift(const Poly& f, mint c) {                   // f(x + c), O(n log n)
    int n = sz(f); if (n == 0) return f;
    vector<mint> fact(n, 1), ifact(n); for (int i = 1; i < n; i++) fact[i] = fact[i - 1] * mint(i);
    ifact[n - 1] = fact[n - 1].inv(); for (int i = n - 1; i > 0; i--) ifact[i - 1] = ifact[i] * mint(i);
    Poly a(n), b(n); mint cp = 1;
    for (int i = 0; i < n; i++) a[n - 1 - i] = f[i] * fact[i], b[i] = cp * ifact[i], cp *= c;
    Poly prod = polyMul(a, b), g(n);
    for (int k = 0; k < n; k++) g[k] = prod[n - 1 - k] * ifact[k];
    return g;
}
vector<mint> samplingShift(const vector<mint>& ys, ll m, int cnt, const Comb& cb) {   // ys[i] = f(i), deg f < n; f(m..m+cnt-1)
    int n = sz(ys); vector<mint> res; if (cnt <= 0) return res;
    if (n == 0) return vector<mint>(cnt, 0);
    while (m < n && sz(res) < cnt) res.push_back(ys[m++]);   // known values
    if (sz(res) < cnt) { auto rest = shiftSamples(ys, mint(m), cnt - sz(res), cb); res.insert(res.end(), all(rest)); }
    return res;
}
vector<mint> binomialSumsAll(const vector<int>& a, int K) { // s[k] = sum_i C(a_i, k), a_i >= 0
    int mx = 0; for (int x : a) mx = max(mx, x);
    Poly p(mx + 1, 0); for (int x : a) p[x] += 1;
    Poly q = taylorShift(p, 1); vector<mint> res(K + 1, 0);
    for (int k = 0; k <= K && k <= mx; k++) res[k] = q[k];
    return res;
}

Kinoshita–Li: power projection and fast composition

Power projection. as a series in truncated at . Bostan–Mori in two variables: multiply numerator and denominator by ; the denominator is even in , so where keeps the coefficients of . Each round halves the -degree and doubles the -degree (truncated at ), so all rounds cost ; at the answer is . Composition is the transpose: (pairing reversed with ) equals , so the linear map is the transpose of (power projection). Running the transposed steps of power projection in reverse order (Tellegen’s principle) computes composition in the same . Reversion: Lagrange inversion turns (one power projection) into , then an -th root by / .

vector<mint> mul2D(const vector<mint>& a, int ax, int ay, const vector<mint>& b, int bx, int by) {   // row-major x * Y + y
    int cy = ay + by - 1, cx = ax + bx - 1;
    Poly fa((ax - 1) * cy + ay, 0), fb((bx - 1) * cy + by, 0);
    for (int x = 0; x < ax; x++) for (int y = 0; y < ay; y++) fa[x * cy + y] = a[x * ay + y];
    for (int x = 0; x < bx; x++) for (int y = 0; y < by; y++) fb[x * cy + y] = b[x * by + y];
    Poly c = polyMul(fa, fb); c.resize(cx * cy, 0); return c;
}
struct GraeffeLevels {                                      // Q_0 = 1 - y g(x), Q_{t+1}(x^2, y) = Q_t(x, y) Q_t(-x, y), truncated
    vector<vector<mint>> Q; vector<int> rows, cols, N;
    GraeffeLevels(const Poly& g, int n, int m) {
        int qy = min(2, m), qx = n, deg = n - 1; vector<mint> q(qx * qy, 0); q[0] = 1;
        if (qy == 2) for (int x = 0; x < qx; x++) q[x * 2 + 1] = -(x < sz(g) ? g[x] : mint(0));
        while (true) {
            Q.push_back(q), rows.push_back(qx), cols.push_back(qy), N.push_back(deg);
            if (deg == 0) break;
            vector<mint> qn = q; for (int x = 1; x < qx; x += 2) for (int y = 0; y < qy; y++) qn[x * qy + y] = -qn[x * qy + y];
            auto prod = mul2D(q, qx, qy, qn, qx, qy);
            int py = 2 * qy - 1, nd = deg / 2, nqx = nd + 1, nqy = min(py, m); vector<mint> v(nqx * nqy);
            for (int k = 0; k < nqx; k++) for (int y = 0; y < nqy; y++) v[k * nqy + y] = prod[(2 * k) * py + y];
            q = v, qx = nqx, qy = nqy, deg = nd;
        }
    }
};
vector<mint> powerProjection(const Poly& w, const Poly& g, int m) {   // a_i = [x^(|w|-1)] w g^i, i < m
    int n = sz(w); if (n == 0 || m == 0) return vector<mint>(m, 0);
    GraeffeLevels lv(g, n, m); vector<mint> p(all(w)); int px = n, py = 1;
    for (int t = 0; t + 1 < sz(lv.Q); t++) {
        int qx = lv.rows[t], qy = lv.cols[t], deg = lv.N[t];
        vector<mint> qn = lv.Q[t]; for (int x = 1; x < qx; x += 2) for (int y = 0; y < qy; y++) qn[x * qy + y] = -qn[x * qy + y];
        auto u = mul2D(p, px, py, qn, qx, qy);
        int uy = py + qy - 1, par = deg & 1, nx = deg / 2 + 1, ny = min(uy, m); vector<mint> np(nx * ny);
        for (int k = 0; k < nx; k++) for (int y = 0; y < ny; y++) np[k * ny + y] = u[(2 * k + par) * uy + y];
        p = np, px = nx, py = ny;
    }
    int qy = lv.cols.back();
    Poly q0(lv.Q.back().begin(), lv.Q.back().begin() + qy), p0(p.begin(), p.begin() + py);
    return mulTrunc(p0, polyInv(q0, m), m);
}
Poly composeFast(Poly f, Poly g, int n) {                   // f(g(x)) mod x^n, O(n log^2 n)
    if (n == 0) return {};
    g = polyTrunc(g, n);
    if (g[0] != mint(0)) f = taylorShift(f, g[0]), g[0] = 0;
    if (sz(f) > n) f.resize(n);                             // g(0) = 0: g^i = O(x^i)
    int m = sz(f); if (m == 0) return Poly(n, 0);
    GraeffeLevels lv(g, n, m); int T = sz(lv.Q) - 1;
    vector<int> D(T + 1, 0); for (int t = 0; t < T; t++) D[t + 1] = min(D[t] + lv.cols[t] - 1, m - 1);   // y-degree bounds
    int qy = lv.cols.back(); Poly q0(lv.Q.back().begin(), lv.Q.back().begin() + qy);
    Poly R = polyInv(q0, m), Rr(R.rbegin(), R.rend()), fb = polyMul(f, Rr);
    vector<mint> B(D[T] + 1); for (int j = 0; j <= D[T]; j++) B[j] = j + m - 1 < sz(fb) ? fb[j + m - 1] : mint(0);
    int by = D[T] + 1;
    for (int t = T - 1; t >= 0; t--) {                       // transposed rounds, last to first
        int qx = lv.rows[t], qyt = lv.cols[t], deg = lv.N[t], par = deg & 1, ey = D[t + 1] + 1, nx = deg / 2 + 1;
        vector<mint> E(qx * ey, 0);
        for (int k = 0; k < nx; k++) for (int y = 0; y < ey && y < by; y++) E[(2 * k + par) * ey + y] = B[k * by + y];
        vector<mint> qr(qx * qyt);                          // Q_t(-x, y) reversed in both variables
        for (int c = 0; c < qx; c++) for (int d = 0; d < qyt; d++) { mint v = lv.Q[t][c * qyt + d]; if (c & 1) v = -v; qr[(qx - 1 - c) * qyt + (qyt - 1 - d)] = v; }
        auto conv = mul2D(E, qx, ey, qr, qx, qyt);
        int cy = ey + qyt - 1, nb = D[t] + 1; vector<mint> nbv(qx * nb);
        for (int a = 0; a < qx; a++) for (int b = 0; b < nb; b++) nbv[a * nb + b] = conv[(a + qx - 1) * cy + (b + qyt - 1)];
        B = nbv, by = nb;
    }
    Poly h(n); for (int j = 0; j < n; j++) h[j] = B[(n - 1 - j) * by];
    return h;
}
Poly compositionalInverseFast(const Poly& g, int n) {       // g(0) = 0, g'(0) != 0, O(n log^2 n)
    if (n <= 1) return Poly(n, 0);
    int N = n - 1; Poly w(n, 0); w[0] = 1;
    auto p = powerProjection(w, polyTrunc(g, n), N + 1);    // p_k = [x^N] g^k
    Poly S(N); for (int j = 0; j < N; j++) S[j] = mint(N) * mint(N - j).inv() * p[N - j];   // (x/h)^N mod x^N
    mint c = S[0].inv(); for (auto& x : S) x *= c;
    Poly lg = polyLog(S, N); mint iN = mint(N).inv(); for (auto& x : lg) x *= iN;
    Poly xh = polyExp(lg, N); for (auto& x : xh) x *= g[1];  // x / h, constant term g'(0)
    Poly inv = polyInv(xh, N), h(n, 0); for (int j = 0; j < N; j++) h[j + 1] = inv[j];
    return h;
}