, 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.
| Operation | Method | Cost |
|---|---|---|
| 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 |
| reversion | power projection + Lagrange inversion + -th root | |
| single coefficient of an inverse | Lagrange: | 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;
}Proof: Brent–Kung, Newton reversion, Taylor shift, binomial sums
Brent–Kung. Write with ; then , and each is a linear combination of the stored ( per block, total), combined by Horner in with truncated multiplications: , minimised near . Reversion: solve ; Newton’s step for a composition is (), doubling the correct coefficients from (correct mod ). Taylor shift: , a convolution of the reversed with . Binomial sums: is shifted by 1, and . Sample shift is proved in Evaluation and Interpolation. All checked against naive composition and direct evaluation.
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;
}Proof: Graeffe power projection, transposition and Lagrange inversion
Power projection. In the ring of polynomials in over formal power series in , the quotient equals ; since the denominator has only even powers of , the coefficient of comes only from numerator terms , giving . Coefficients of above the current never matter and -powers are discarded (the answer is wanted mod ), so the sizes stay per round as halves while the -degree doubles, and each round is one 2D product: per round, rounds. When , . With and , , which is the claim.
Composition as transpose. Both maps are linear: and with . Then , so with the index reversal . Power projection is a composition of linear maps in (2D multiplications by fixed , extraction of parity classes and truncations, the final multiplication by );
composeFastapplies their transposes in reverse order: a transposed multiplication by a fixed polynomial is a multiplication by its reversal followed by extracting the middle coefficients (conv[(a + qx - 1)\cdot cy + (b + qyt - 1)]), the transpose of a parity extraction is the corresponding injection (E), and the first step is the transposed series multiplicationf * Rr. Transposition preserves the cost (Tellegen’s principle). A non-zero is first moved into by a Taylor shift. Verified against Brent–Kung on random inputs of many sizes.Lagrange inversion. For with compositional inverse and : . Proof with formal residues ( for Laurent series): , and for of valuation 1, (check on : for the right side is the residue of a derivative, for it is ). Then ; substituting gives . So the projection yields for , i.e. . Its constant term is ; taking and multiplying by (the constant of ) gives (the -th root with that constant is unique), and . Needs . Verified: for random , against the Newton version.
Related notes
- Linear Recurrences (Bostan–Mori in one variable)
- Generating Functions (Lagrange inversion for trees)
- Evaluation and Interpolation