A segment tree over the -axis where each node stores one line — the one that is best at that node’s midpoint. Insert and query are both , with no requirement that slopes or queries arrive in any order.

The insight

At any node covering with midpoint , keep the line that is better at . When inserting a new line:

  1. If the new line beats the stored one at , swap them.
  2. Two lines cross at most once, so the loser can only win on one side of . Determine which side by comparing at (or ), and recurse into that child only.

One recursion branch per level → .

struct Line {
    long long m = 0, c = LLONG_MAX;                  // identity: +infinity
    long long operator()(long long x) const { return c == LLONG_MAX ? LLONG_MAX : m * x + c; }
};
 
struct LiChao {
    int n;
    vector<Line> t;
    LiChao(int n) : n(n), t(4 * n) {}
 
    void insert(int node, int l, int r, Line nw) {
        int m = (l + r) / 2;
        bool atMid  = nw(m) < t[node](m);
        bool atLeft = nw(l) < t[node](l);
        if (atMid) swap(t[node], nw);
        if (l == r) return;
        if (atLeft != atMid) insert(2*node, l, m, nw);
        else                 insert(2*node+1, m+1, r, nw);
    }
    long long query(int node, int l, int r, int x) {
        long long res = t[node](x);
        if (l == r) return res;
        int m = (l + r) / 2;
        return min(res, x <= m ? query(2*node, l, m, x) : query(2*node+1, m+1, r, x));
    }
};

Why it beats the convex hull trick

CHT (monotone)CHT (dynamic)Li Chao
Slopes must be sortedyesnono
Queries must be sortedyes (pointer) / no (binary search)nono
Insert am.
Query /
Overflow risk in the comparisonhighhighnone
Code lengthshortlong and fiddly~25 lines
Handles non-linear functionsnonoyes

The overflow point is worth emphasising: CHT’s “is this line unnecessary” test multiplies differences of slopes and intercepts, which overflows long long for realistic inputs and needs __int128. Li Chao only ever evaluates lines and compares the results, so it never overflows beyond the function values themselves.

Beyond lines

The algorithm needs only that any two stored functions cross at most once over the domain. So it works for:

  • lines,
  • any family of monotone functions that pairwise cross once,
  • functions like or with suitable parameters,
  • piecewise-defined “cost of using option at time ” functions.

This is a real advantage over the convex hull trick, which is specific to lines.

Dynamic (sparse) Li Chao

When the range is , allocate nodes lazily instead of preallocating :

struct Node { Line ln; int l = -1, r = -1; };
vector<Node> pool;
// create children on demand inside insert()

memory. This is the usual version in practice.

Line insertion over a range

A useful extension: “this line is only valid for ”. Descend as in a segment tree update to the canonical nodes covering , and run the ordinary Li Chao insertion at each. Insert becomes , query stays .

This solves problems where each candidate transition is only available in part of the domain — for instance “the -th offer applies only for quantities between and ”.

Maximum instead of minimum

Flip every comparison, or negate both and on insertion and negate the query result. Negating is less error-prone.

See also: Convex Hull Trick · Segment Tree · Slope Trick