Optimise a linear objective subject to linear inequalities:

General LPs: the simplex method (exponential worst case, a few times pivots in practice). Fixed dimension with many constraints: Seidel’s randomized algorithm in expected , or half-plane intersection when the whole region is needed.

HaveConvert to standard form
minimise maximise
two inequalities and
free variable with
two inequalities
minimise new variable , constraints , minimise

Duality

Primal: , , . Dual: , , .

  • Weak duality: every primal value every dual value.
  • Strong duality: if the primal has a finite optimum, so does the dual, and they are equal.
  • Complementary slackness: at optima, primal constraint is tight, and dual constraint is tight.
  • Total unimodularity: if every square submatrix of has determinant in (network matrices, bipartite incidence matrices, interval matrices with consecutive ones) and is integral, every vertex of is integral: the LP relaxation solves the integer problem, usually via flows.

Duality explains max-flow = min-cut and Kőnig’s theorem, gives potentials in min-cost flow, and lets you swap a hard LP for an easier dual: many “minimum cost to satisfy covering constraints” problems are duals of flows (Flow Modeling).

Simplex (two-phase, Bland’s rule)

LPSolver(A, b, c).solve(x) returns the optimum and fills ; it returns if infeasible and if unbounded. per pivot; fine for up to a few hundred. Phase 1 finds a feasible basis when some (the origin is infeasible).

using T = long double; using vd = vector<T>; using vvd = vector<vd>;
const T LP_EPS = 1e-9, LP_INF = numeric_limits<T>::infinity();
 
struct LPSolver {                                          // maximize c x, A x <= b, x >= 0 (KACTL-style)
    int m, n; vector<int> N, B; vvd D;
    LPSolver(const vvd& A, const vd& b, const vd& c) : m(sz(b)), n(sz(c)), N(n + 1), B(m), D(m + 2, vd(n + 2)) {
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) D[i][j] = A[i][j];
        for (int i = 0; i < m; i++) { B[i] = n + i; D[i][n] = -1; D[i][n + 1] = b[i]; }
        for (int j = 0; j < n; j++) { N[j] = j; D[m][j] = -c[j]; }
        N[n] = -1; D[m + 1][n] = 1;
    }
    void pivot(int r, int s) {
        T inv = 1 / D[r][s];
        for (int i = 0; i < m + 2; i++) if (i != r && fabsl(D[i][s]) > LP_EPS) {
            T f = D[i][s] * inv;
            for (int j = 0; j < n + 2; j++) D[i][j] -= D[r][j] * f;
            D[i][s] = D[r][s] * f;
        }
        for (int j = 0; j < n + 2; j++) if (j != s) D[r][j] *= inv;
        for (int i = 0; i < m + 2; i++) if (i != r) D[i][s] *= -inv;
        D[r][s] = inv; swap(B[r], N[s]);
    }
    bool simplex(int phase) {
        int x = m + phase - 1;
        while (true) {
            int s = -1;
            for (int j = 0; j <= n; j++) if (N[j] != -phase) if (s == -1 || make_pair(D[x][j], N[j]) < make_pair(D[x][s], N[s])) s = j;
            if (D[x][s] >= -LP_EPS) return true;          // optimal
            int r = -1;
            for (int i = 0; i < m; i++) {
                if (D[i][s] <= LP_EPS) continue;
                if (r == -1 || make_pair(D[i][n + 1] / D[i][s], B[i]) < make_pair(D[r][n + 1] / D[r][s], B[r])) r = i;
            }
            if (r == -1) return false;                     // unbounded
            pivot(r, s);
        }
    }
    T solve(vd& x) {
        int r = 0;
        for (int i = 1; i < m; i++) if (D[i][n + 1] < D[r][n + 1]) r = i;
        if (D[r][n + 1] < -LP_EPS) {                       // phase 1: find a feasible basis
            pivot(r, n);
            if (!simplex(2) || D[m + 1][n + 1] < -LP_EPS) return -LP_INF;   // infeasible
            for (int i = 0; i < m; i++) if (B[i] == -1) {
                int s = 0;
                for (int j = 1; j <= n; j++) if (make_pair(D[i][j], N[j]) < make_pair(D[i][s], N[s])) s = j;
                pivot(i, s);
            }
        }
        bool ok = simplex(1); x = vd(n);
        for (int i = 0; i < m; i++) if (B[i] < n) x[B[i]] = D[i][n + 1];
        return ok ? D[m][n + 1] : LP_INF;
    }
};
// maximize 3x + 2y s.t. x + y <= 4, x + 3y <= 6, x, y >= 0  ->  12 at x = (4, 0)

Pitfalls. Variables are implicitly (split free variables); equalities become two inequalities; use long double and , and scale constraints to similar magnitudes; degenerate pivots can cycle, which Bland’s rule (smallest index among ties) prevents; distinguish infeasible from unbounded in the output; the answer is floating point: round only if integrality is guaranteed (TU). Integer LP is NP-hard.

Seidel’s LP in fixed dimension

Shuffle the constraints and add them one at a time, maintaining the current optimum . If satisfies the new constraint, nothing changes (the common case). Otherwise the new optimum lies on the boundary of the new constraint: project the earlier constraints onto that hyperplane and solve a -dimensional LP. Expected time , space ; the deterministic alternative (Megiddo) is . Below is the 2D version, with a bounding box that makes every LP bounded.

struct HP { long double a, b, c; };                        // a x + b y <= c
 
// maximize cx * x + cy * y subject to the half-planes and |x|, |y| <= M. Returns {feasible, (x, y)}.
pair<bool, pair<long double, long double>> seidel2D(vector<HP> H, long double cx, long double cy, long double M, mt19937_64& rng) {
    const long double eps = 1e-9;
    shuffle(all(H), rng);
    vector<HP> box = {{1, 0, M}, {-1, 0, M}, {0, 1, M}, {0, -1, M}};
    H.insert(H.begin(), all(box));
    long double x = cx >= 0 ? M : -M, y = cy >= 0 ? M : -M;   // optimum of the box alone
    for (int i = 4; i < sz(H); i++) {
        auto [a, b, c] = H[i];
        if (a * x + b * y <= c + eps) continue;
        long double l2 = a * a + b * b;
        if (l2 < eps) return {false, {0, 0}};               // 0 <= c violated
        long double px = a * c / l2, py = b * c / l2, dx = -b, dy = a;   // the line: (px, py) + t (dx, dy)
        long double lo = -1e30, hi = 1e30;
        for (int j = 0; j < i; j++) {
            long double num = H[j].c - (H[j].a * px + H[j].b * py), den = H[j].a * dx + H[j].b * dy;
            if (fabsl(den) < eps) { if (num < -eps) return {false, {0, 0}}; continue; }
            if (den > 0) hi = min(hi, num / den); else lo = max(lo, num / den);
        }
        if (lo > hi + eps) return {false, {0, 0}};
        long double dir = cx * dx + cy * dy, t = dir > 0 ? hi : lo;
        x = px + t * dx; y = py + t * dy;
    }
    return {true, {x, y}};
}

Backwards analysis is the real lesson: after constraints the optimum is determined by of them, and in random order the -th constraint is one of those with probability , so . The same argument gives Welzl’s circle (), randomized incremental Delaunay ( expected change per insertion), randomized quicksort () and treap depth (). Seidel’s algorithm extends to every LP-type problem (combinatorial dimension , monotonicity and locality): minimum enclosing ball and ellipsoid, smallest enclosing annulus, distance between polytopes (Sharir–Welzl; Matoušek–Sharir–Welzl).

Contest use of low-dimensional LP
is the intersection of half-planes non-empty?2D LP feasibility
Chebyshev centre (largest inscribed circle of a convex polygon)3D LP in
smallest enclosing circleLP-type: Welzl
line fitting under (minimise the maximum vertical error)2D LP
difference constraintsbetter with Bellman–Ford
”minimise a max of linear functions” in 1 variableternary search (convex); 2–3 variables: nested ternary search or LP
fractional relaxationsbounds in branch and bound