Converting between coefficients and values: evaluation at arbitrary points and interpolation in with a subproduct tree, Lagrange interpolation at consecutive points (sums of powers, “the answer is a polynomial in ”), shifting sample points, in , and the Newton basis. Uses Poly, polyMul, polyDivMod, derivative, evalPoly from NTT and Power Series, Comb from Modular Arithmetic.

TaskNaiveFast
evaluate at 1 point Horner
evaluate at consecutive points / shift samples , one convolution
evaluate at points in geometric progression chirp-z (Convolution Variants)
evaluate at arbitrary points subproduct tree
interpolate from arbitrary points (coefficients)
value at one new point from consecutive samples Lagrange
shift (Taylor Shift)

Check the point structure first

Consecutive points give interpolation and evaluation; geometric points give chirp-z; only genuinely arbitrary points need the tree, whose constant (several NTTs per node) makes it slower than Horner below . In contests the points are usually .

vector<Poly> subproductTree(const vector<mint>& xs) {       // tree[1] = prod (x - x_i); node p covers a contiguous range
    int n = sz(xs); vector<Poly> t(4 * max(n, 1));
    auto build = [&](auto&& self, int p, int l, int r) -> void {
        if (r - l == 1) { t[p] = {-xs[l], 1}; return; }
        int m = (l + r) / 2; self(self, 2 * p, l, m); self(self, 2 * p + 1, m, r);
        t[p] = polyMul(t[2 * p], t[2 * p + 1]);
    };
    if (n) build(build, 1, 0, n);
    return t;
}
vector<mint> multipointEval(const Poly& f, const vector<mint>& xs) {   // O(n log^2 n): remainders down the tree
    int n = sz(xs); if (n == 0) return {};
    auto t = subproductTree(xs); vector<mint> res(n);
    auto down = [&](auto&& self, int p, int l, int r, Poly g) -> void {
        g = polyDivMod(g, t[p]).second;                     // g = f mod prod_{i in [l,r)} (x - x_i)
        if (r - l <= 32) { for (int i = l; i < r; i++) res[i] = evalPoly(g, xs[i]); return; }
        int m = (l + r) / 2; self(self, 2 * p, l, m, g); self(self, 2 * p + 1, m, r, g);
    };
    down(down, 1, 0, n, f);
    return res;
}
Poly interpolate(const vector<mint>& xs, const vector<mint>& ys) {   // distinct xs, O(n log^2 n)
    int n = sz(xs); if (n == 0) return {};
    auto t = subproductTree(xs);
    vector<mint> d = multipointEval(derivative(t[1]), xs);  // M'(x_i) = prod_{j != i} (x_i - x_j)
    auto up = [&](auto&& self, int p, int l, int r) -> Poly {
        if (r - l == 1) return {ys[l] / d[l]};
        int m = (l + r) / 2;
        Poly a = polyMul(self(self, 2 * p, l, m), t[2 * p + 1]), b = polyMul(self(self, 2 * p + 1, m, r), t[2 * p]);
        if (sz(a) < sz(b)) swap(a, b); for (int i = 0; i < sz(b); i++) a[i] += b[i];
        return a;
    };
    Poly res = up(up, 1, 0, n); res.resize(n); return res;
}
 
mint lagrangeGeneral(const vector<mint>& xs, const vector<mint>& ys, mint t) {   // O(n^2), one inverse
    int n = sz(xs); mint num = 0, den = 1; vector<mint> dens(n);
    for (int i = 0; i < n; i++) { if (xs[i] == t) return ys[i]; mint d = t - xs[i]; for (int j = 0; j < n; j++) if (j != i) d *= xs[i] - xs[j]; dens[i] = d; }
    vector<mint> pre(n + 1, 1); for (int i = 0; i < n; i++) pre[i + 1] = pre[i] * dens[i];   // batch inverse
    mint inv = pre[n].inv(), full = 1; for (int i = 0; i < n; i++) full *= t - xs[i];
    for (int i = n - 1; i >= 0; i--) { num += ys[i] * pre[i] * inv; inv *= dens[i]; }
    return num * full;
}
mint lagrangeConsecutive(const vector<mint>& y, ll k, const Comb& cb) {   // samples at x = 0..n-1, value at k (any ll), O(n)
    int n = sz(y); if (k >= 0 && k < n) return y[k];
    vector<mint> pre(n + 1, 1), suf(n + 1, 1);
    for (int i = 0; i < n; i++) pre[i + 1] = pre[i] * mint(k - i);
    for (int i = n - 1; i >= 0; i--) suf[i] = suf[i + 1] * mint(k - i);
    mint res = 0;
    for (int i = 0; i < n; i++) { mint term = y[i] * pre[i] * suf[i + 1] * cb.fi[i] * cb.fi[n - 1 - i]; res += (n - 1 - i) % 2 ? -term : term; }
    return res;
}
mint powerSum(ll n, int k, const Comb& cb) {               // sum_{i=1}^{n} i^k, O(k log) for any n
    vector<mint> y(k + 2, 0);
    for (int i = 1; i <= k + 1; i++) y[i] = y[i - 1] + mint(i).pow(k);
    return lagrangeConsecutive(y, n, cb);
}
Poly shiftSamples(const Poly& y, mint c, int m, const Comb& cb) {   // f(0..n-1) -> f(c), ..., f(c+m-1) for deg f < n; needs c + i not in [0, n) and cb >= n
    int n = sz(y); Poly a(n), b(n + m - 1);
    for (int i = 0; i < n; i++) { a[i] = y[i] * cb.fi[i] * cb.fi[n - 1 - i]; if ((n - 1 - i) % 2) a[i] = -a[i]; }
    vector<mint> v(n + m - 1); for (int j = 0; j < n + m - 1; j++) v[j] = c - mint(n - 1) + mint(j);   // c - (n-1) + j
    vector<mint> pre(n + m, 1); for (int j = 0; j < n + m - 1; j++) pre[j + 1] = pre[j] * v[j];
    mint inv = pre[n + m - 1].inv();
    for (int j = n + m - 2; j >= 0; j--) { b[j] = inv * pre[j]; inv *= v[j]; }   // b_j = 1 / (c - (n-1) + j)
    Poly conv = polyMul(a, b), res(m);
    mint prod = 1; for (int j = 0; j < n; j++) prod *= c - mint(j);   // prod_{j<n} (c + 0 - j)
    for (int k = 0; k < m; k++) {
        res[k] = conv[n - 1 + k] * prod;                    // f(c+k) = prod_{j<n}(c+k-j) * sum_i a_i / (c+k-i)
        prod *= (c + mint(k + 1)) / (c + mint(k + 1) - mint(n));
    }
    return res;
}
mint factorialMod(ll n) {                                   // n! mod 998244353, O(sqrt n log^2 n); n < p
    if (n <= 1) return 1;
    ll v = max(1LL, (ll)sqrtl((long double)n));
    vector<mint> rs(v); for (ll i = 0; i < v; i++) rs[i] = -mint(i + 1);   // f(x) = (x+1)...(x+v)
    Poly f = subproductTree(rs)[1];
    vector<mint> xs(v); for (ll i = 0; i < v; i++) xs[i] = mint(i * v);
    mint res = 1; for (auto val : multipointEval(f, xs)) res *= val;   // covers 1..v^2
    for (ll j = v * v + 1; j <= n; j++) res *= mint(j);
    return res;
}
What the tree enables
coefficient value representationsboth directions
fast polynomial gcd (half-gcd), factorisation, Chinese remaindering of polynomialsthe same tree shape
for (with a large NTT-friendly or CRT-split modulus)
integer CRT from many moduli, batch modular inverse (linear special case), segment trees of productsinteger analogues

Recognising “the answer is a polynomial in ”

Signals: the answer is a sum of a polynomial over a range (degree goes up by 1); it counts lattice points in a region scaling linearly with (Ehrhart polynomials); a DP whose answer is eventually polynomial; huge with a structure of small degree. Verification: compute the first values by brute force, interpolate from the first , and check the predictions on the rest. Pitfalls: repeated (deduplicate), a wrong degree guess (silently wrong answers: verify with extra points), a target equal to a sample point (return it directly).

Newton basis and divided differences

; the are divided differences (with repeated nodes: confluent / Hermite data). The table costs , but adding a point is , useful when the sample set grows. Fast conversion: split the nodes in halves with ; then with , the left coefficients are those of rem and the right ones those of on the right nodes; backwards, (Library Checker “Conversion from Monomial Basis to Newton Basis”).

struct NewtonInterpolator {                                 // add points one at a time, O(n) each; distinct x
    vector<mint> xs, c, diag;                              // diag[j] = f[x_j..x_last]
    void add(mint x, mint y) {
        int n = sz(xs); xs.push_back(x); diag.push_back(y);
        for (int j = n - 1; j >= 0; j--) diag[j] = (diag[j + 1] - diag[j]) / (x - xs[j]);
        c.push_back(diag[0]);
    }
    mint eval(mint t) const { mint r = 0; for (int i = sz(c) - 1; i >= 0; i--) r = r * (t - xs[i]) + c[i]; return r; }
};
vector<mint> toNewtonBasis(const Poly& f, const vector<mint>& xs) {   // deg f < n = |xs|, O(n log^2 n)
    int n = sz(xs); if (n == 0) return {};
    auto t = subproductTree(xs); vector<mint> c(n);
    auto rec = [&](auto&& self, int p, int l, int r, Poly g) -> void {
        g.resize(r - l);
        if (r - l == 1) { c[l] = g[0]; return; }
        int m = (l + r) / 2; auto [q, rem] = polyDivMod(g, t[2 * p]);   // g = rem + P_left q
        self(self, 2 * p, l, m, rem); self(self, 2 * p + 1, m, r, q);
    };
    rec(rec, 1, 0, n, f);
    return c;
}
Poly fromNewtonBasis(const vector<mint>& c, const vector<mint>& xs) {
    int n = sz(xs); if (n == 0) return {};
    auto t = subproductTree(xs);
    auto rec = [&](auto&& self, int p, int l, int r) -> Poly {
        if (r - l == 1) return {c[l]};
        int m = (l + r) / 2; Poly a = self(self, 2 * p, l, m), b = polyMul(t[2 * p], self(self, 2 * p + 1, m, r));
        if (sz(a) < sz(b)) swap(a, b); for (int i = 0; i < sz(b); i++) a[i] += b[i];
        return a;
    };
    Poly res = rec(rec, 1, 0, n); res.resize(n); return res;
}