Solving and computing numerically. For contest purposes, binary search with a fixed iteration count is often the right root finder (slower per step, unconditionally correct); Newton is the tool when speed or precision doubling matters, and it reappears everywhere as a doubling scheme.

MethodConvergenceNeeds Note
bisectionlinear, 1 bit per stepnoalways works given a sign change
Newtonquadratic near a simple rootyescorrect digits double each step
secantsuperlinear, order nofinite difference instead of
regula falsilinear to superlinearno
Brent / safeguarded Newtonsuperlinear, guaranteedno / yeshybrid with a bracket
fixed-point iteration linear if no

Newton’s method

FailureSymptomFix
huge jump, divergencebisection fallback
bad starting pointanother root, or divergencebracket first
multiple root ( at the root)only linear convergence for multiplicity
oscillationcycles between two pointsdamping or a hybrid

Optimization via Newton: applying it to , , finds a stationary point quadratically, but any critical point, not necessarily a minimum; ternary search is safer for unimodal functions in contests.

Newton beyond real numbers. The same iteration works wherever there is a “derivative”, and each step doubles the precision:

SettingIterationDoubles
realscorrect digits
power series inversecorrect coefficients: Power Series
power series , , , coefficients
-adic (Hensel lifting)the power of : Modular Square Roots
matrix inverse (Newton–Schulz)correct digits
big-integer reciprocalbits: how big-number division is implemented

Because the last doubling step dominates, the total cost is , the cost of one multiplication: this turns “invert a power series in ” into a solved problem.

template<class F, class D> double newton(F f, D df, double x, int iters = 60) {
    for (int i = 0; i < iters; i++) {
        double d = df(x); if (d == 0) break;
        double nx = x - f(x) / d;
        if (fabs(nx - x) < 1e-15 * max(1.0, fabs(x))) return nx;
        x = nx;
    }
    return x;
}
 
template<class F, class D> double safeNewton(F f, D df, double lo, double hi) {   // f(lo), f(hi) of opposite signs
    double x = (lo + hi) / 2; bool negLo = f(lo) < 0;
    for (int i = 0; i < 200; i++) {
        double fx = f(x); if (fx == 0) return x;
        if ((fx < 0) == negLo) lo = x; else hi = x;
        double d = df(x), nx = d != 0 ? x - fx / d : (lo + hi) / 2;
        x = (nx > lo && nx < hi) ? nx : (lo + hi) / 2;     // fall back to bisection
    }
    return x;
}
 
ll isqrtFloor(ll n) {                                      // floor(sqrt(n)), n >= 0: correct the double guess
    ll x = (ll)sqrtl((long double)n);
    while (x > 0 && (i128)x * x > n) x--;
    while ((i128)(x + 1) * (x + 1) <= n) x++;
    return x;
}
 
ull isqrtNewton(ull n) {                                   // floor(sqrt(n)) with integer Newton steps
    if (n < 2) return n;
    ull x = n, y = x / 2 + (x & 1);                        // (x + n / x) / 2 for x = n, without overflow
    while (y < x) x = y, y = (x + n / x) / 2;
    return x;
}
 
i128 ipowCap(ll b, int k, i128 cap) { i128 r = 1; while (k--) { r *= b; if (r > cap) return cap + 1; } return r; }
ll iroot(ll n, int k) {                                    // floor(n^(1/k)), n >= 0, k >= 1
    ll x = (ll)powl((long double)n, 1.0L / k);
    while (x > 0 && ipowCap(x, k, n) > n) x--;
    while (ipowCap(x + 1, k, n) <= n) x++;
    return x;
}

Always correct the result of sqrt / pow: on a double, sqrt(n) for near is often off by one, a classic wrong answer; the adjustment loops cost nothing.

Polynomial roots

All distinct real roots of a polynomial, by recursing on the derivative and bisecting monotone pieces. polyRoots(p) searches (Cauchy’s bound); polyRoots(p, lo, hi) only . Cost for degree : derivative levels, up to pieces each, 100 bisection steps of .

double evalPoly(const vector<double>& p, double x) { double r = 0; for (int i = sz(p) - 1; i >= 0; i--) r = r * x + p[i]; return r; }
 
vector<double> polyRoots(vector<double> p, double lo, double hi) {   // p[i] = coefficient of x^i
    while (sz(p) > 1 && p.back() == 0) p.pop_back();
    int d = sz(p) - 1;
    if (d <= 0) return {};
    if (d == 1) { double r = -p[0] / p[1]; if (lo <= r && r <= hi) return {r}; return {}; }
    vector<double> der(d);
    for (int i = 1; i <= d; i++) der[i - 1] = p[i] * i;
    vector<double> pts = polyRoots(der, lo, hi);           // critical points split [lo, hi] into monotone pieces
    pts.insert(pts.begin(), lo); pts.push_back(hi);
    auto nearZero = [&](double x) {                        // |p(x)| within Horner's rounding error bound
        double scale = 0, pw = 1;
        for (double c : p) scale += fabs(c) * pw, pw *= max(1.0, fabs(x));
        return fabs(evalPoly(p, x)) <= 4 * d * DBL_EPSILON * scale;
    };
    vector<double> roots;
    for (int i = 0; i < sz(pts); i++) {
        if (nearZero(pts[i])) { roots.push_back(pts[i]); continue; }  // even-multiplicity roots touch zero here
        if (i + 1 == sz(pts) || nearZero(pts[i + 1])) continue;
        double l = pts[i], h = pts[i + 1]; bool negLeft = evalPoly(p, l) < 0;
        if (negLeft == (evalPoly(p, h) < 0)) continue;
        for (int it = 0; it < 100; it++) { double mid = (l + h) / 2; ((evalPoly(p, mid) < 0) == negLeft ? l : h) = mid; }
        roots.push_back((l + h) / 2);
    }
    sort(all(roots));
    vector<double> res;
    for (double r : roots) if (res.empty() || r - res.back() > 1e-9 * max(1.0, fabs(r))) res.push_back(r);
    return res;
}
 
vector<double> polyRoots(vector<double> p) {
    while (sz(p) > 1 && p.back() == 0) p.pop_back();
    double bound = 1;
    for (int i = 0; i + 1 < sz(p); i++) bound = max(bound, 1 + fabs(p[i] / p.back()));
    return polyRoots(p, -bound, bound);
}

Roots of even multiplicity do not change sign: a critical point with (Horner’s error bound) is reported as a root, and roots closer than (relative) are merged. So a multiple root is reported once, as accurately as a simple root of the derivative; two distinct roots closer than about may merge, and a local extremum within the error bound of 0 is reported as a root. Accuracy depends on conditioning: in tests (degree , roots in at least apart) the error stayed below . Use long double or rescale for clustered roots, high degree or huge coefficients, and strip leading coefficients that are tiny only because of rounding.

Integration

MethodErrorExact forNote
rectangle (midpoint)linearsimplest
trapezoidlinear
Simpsoncubicsthe default
Simpson 3/8cubics
Romberghigh degreetrapezoid + repeated Richardson extrapolation
Gauss–Legendre ( points)exact to degree best per evaluation, needs tabulated nodes
Monte Carlodimension-free: the only option in high dimensions

template<class F> double simpson(F f, double a, double b, int n) {   // composite, n even
    double h = (b - a) / n, s = f(a) + f(b);
    for (int i = 1; i < n; i++) s += f(a + i * h) * (i % 2 ? 4 : 2);
    return s * h / 3;
}
 
template<class F> double adaptiveSimpson(F f, double a, double b, double eps, double whole, double fa, double fm, double fb, int depth) {
    double c = (a + b) / 2, l = (a + c) / 2, r = (c + b) / 2, fl = f(l), fr = f(r);
    double left = (c - a) / 6 * (fa + 4 * fl + fm), right = (b - c) / 6 * (fm + 4 * fr + fb);
    if (depth <= 0 || fabs(left + right - whole) <= 15 * eps) return left + right + (left + right - whole) / 15;   // Richardson
    return adaptiveSimpson(f, a, c, eps / 2, left, fa, fl, fm, depth - 1) + adaptiveSimpson(f, c, b, eps / 2, right, fm, fr, fb, depth - 1);
}
template<class F> double integrate(F f, double a, double b, double eps = 1e-10) {
    double fa = f(a), fm = f((a + b) / 2), fb = f(b);
    return adaptiveSimpson(f, a, b, eps, (b - a) / 6 * (fa + 4 * fm + fb), fa, fm, fb, 50);
}

Adaptive Simpson spends effort where the function is difficult and almost none where it is flat; reusing the already computed values halves the evaluations, and the /15 term is Richardson extrapolation, which upgrades the accuracy for free.

Integration in contestsIntegrand
area under a curve, between curves, the difference
arc length
volume of revolution
volume by cross-sections (the most common)area of the 2D slice at , often itself a geometric computation
expected value, probability of a region, the density
union of circles / shapesslice length or area as a function of

Simpson assumes smoothness

Adaptive Simpson silently gives wrong answers on functions with discontinuities or kinks: the error estimate compares two smooth approximations that happen to agree. Split the interval at every known breakpoint (where shapes start or stop overlapping, where the maximum changes which curve is on top) and integrate each smooth piece. Also: remove endpoint singularities by substitution, and give oscillatory integrands a finer base subdivision (aliasing).

Exact alternatives, check first: polygon area by the shoelace formula; circle–polygon intersection by closed-form sectors and triangles; piecewise-linear functions as sums of trapezoids; polynomial integrands symbolically. Reach for numerical integration only when there is no closed form. Monte Carlo: with error independent of the dimension (4 times the samples per extra digit); variance reduction (importance sampling, stratification, quasi-random sequences) helps a lot.

Newton–Schulz: matrix inverse by multiplications

The scalar analogue is Newton on : , convergent from any start with . Per iteration: two matrix multiplications, (or ); iterations: to enter the convergence basin plus . Sequentially worse than Gauss–Jordan; it wins where multiplication is much cheaper than elimination: GPUs and distributed systems (embarrassingly parallel GEMMs versus sequential pivoting), settings without division (fixed-point hardware, homomorphic encryption, some rings), and refinement (one step squares the error of an approximate inverse from any source). The same iteration converges to the Moore–Penrose pseudo-inverse for rectangular , and relatives compute the matrix sign function and the polar decomposition.

vector<vector<double>> newtonSchulzInverse(const vector<vector<double>>& A, int iters = 100) {
    int n = sz(A); double n1 = 0, ninf = 0;
    for (int j = 0; j < n; j++) { double s = 0; for (int i = 0; i < n; i++) s += fabs(A[i][j]); n1 = max(n1, s); }
    for (int i = 0; i < n; i++) { double s = 0; for (int j = 0; j < n; j++) s += fabs(A[i][j]); ninf = max(ninf, s); }
    auto mul = [&](const vector<vector<double>>& P, const vector<vector<double>>& Q) {
        vector<vector<double>> R(n, vector<double>(n, 0));
        for (int i = 0; i < n; i++) for (int k = 0; k < n; k++) for (int j = 0; j < n; j++) R[i][j] += P[i][k] * Q[k][j];
        return R;
    };
    vector<vector<double>> X(n, vector<double>(n));
    for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) X[i][j] = A[j][i] / (n1 * ninf);
    for (int it = 0; it < iters; it++) {
        auto AX = mul(A, X); double err = 0;
        for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { AX[i][j] = (i == j ? 2 : 0) - AX[i][j]; err += fabs(AX[i][j] - (i == j)); }
        X = mul(X, AX);
        if (err < 1e-13 * n) break;                         // AX was already the identity
    }
    return X;
}