Three tools for :

  • monotone deque when and ranges over a window whose ends only move right: ;
  • binary-search decision queue (“1D1D”) when satisfies the quadrangle inequality and is computed online: ;
  • SMAWK for row minima of a totally monotone matrix known in advance: ; LARSCH extends it to the online case in .

Sliding-window minimum

deque<int> dq;                                             // indices j with increasing dp[j] + f(j)
for (int i = 1; i <= n; i++) {
    int j = i - 1; while (sz(dq) && val(dq.back()) >= val(j)) dq.pop_back(); dq.push_back(j);   // val(j) = dp[j] + f(j)
    while (dq.front() < left(i)) dq.pop_front();                                               // left(i) nondecreasing
    dp[i] = val(dq.front()) + g(i);
}

Uses: jumps of length at most , segments of length in (push when it becomes available), bounded knapsack per residue class (Knapsack), max subarray with length limit. If the window’s left end can move left, use a segment tree.

The window doesn’t have to be a fixed width — only that the valid range of is contiguous and moves monotonically:

Condition on valid How the ends move
fixed width
two pointers find the left end
(sorted )two pointers
, lastBad non-decreasingtwo pointers

This same deque is also the standard -amortized minimum queue outside DP entirely (Monotonic Queue), which is what makes Mo’s-algorithm variants with a removable-min aggregate and some graph algorithms fast (Mo’s Algorithm).

Debugging a monotone-queue optimization

Four checks, in order: (1) is the window’s left end monotone non-decreasing — if not, the deque is simply wrong, not slow; (2) popping the front happens before querying, popping the back before pushing; (3) >= vs > in the back-pop comparison only affects deque length, not correctness, so don’t chase a bug there; (4) dp[i] is pushed after it’s computed — pushing before creates a self-dependency.

1D1D with the quadrangle inequality

If for , then a newer decision , once better than an older decision at some point, stays better for all later points. Keep a stack of segments (first position, decision); a new decision takes over a suffix found by binary search.

template<class T, class W> vector<T> oneD1D(int n, T dp0, W w) {   // dp[x] = min_{i<x} dp[i] + w(i, x)
    vector<T> dp(n + 1); dp[0] = dp0;
    vector<pair<int, int>> seg{{1, 0}};                      // (first x where decision is best, decision)
    auto val = [&](int i, int x) { return dp[i] + w(i, x); };
    for (int x = 1; x <= n; x++) {
        int k = prev(upper_bound(all(seg), pair(x, INT_MAX)))->second;
        dp[x] = val(k, x);
        if (x == n) break;
        while (true) {
            if (seg.empty()) { seg.push_back({x + 1, x}); break; }
            auto [y, old] = seg.back(); int st = max(y, x + 1);
            if (val(x, st) < val(old, st)) { if (y > x) { seg.pop_back(); continue; } seg.push_back({x + 1, x}); break; }
            int lo = st + 1, hi = n + 1;                       // first position where x beats old
            while (lo < hi) { int m = (lo + hi) / 2; if (val(x, m) < val(old, m)) hi = m; else lo = m + 1; }
            if (lo <= n) seg.push_back({lo, x});
            break;
        }
    }
    return dp;
}

Typical costs: or with (“poetry” / line breaking), in maximisation. For concave costs in minimisation (reverse QI), a new decision wins a prefix of each older decision’s region instead: process with a deque where new decisions are compared against the front, or use LARSCH-style structures.

SMAWK

Named for its five authors (Aggarwal, Klawe, Moran, Shor, Wilber). Row minima of an totally monotone matrix (if a later column beats an earlier one in some row, it also does in every later row; implied by the Monge condition ) in evaluations: reduce the columns to at most candidates with a stack, recurse on the odd rows, then fill each even row by scanning between its neighbours’ answers.

template<class F> vector<int> smawk(int n, int m, F A) {    // leftmost argmin of every row
    auto rec = [&](auto&& self, const vector<int>& rows, const vector<int>& cols) -> vector<int> {
        if (rows.empty()) return {};
        vector<int> st;
        for (int c : cols) {
            while (sz(st) && A(rows[sz(st) - 1], st.back()) > A(rows[sz(st) - 1], c)) st.pop_back();
            if (sz(st) < sz(rows)) st.push_back(c);
        }
        vector<int> odd; for (int i = 1; i < sz(rows); i += 2) odd.push_back(rows[i]);
        vector<int> so = self(self, odd, st), res(sz(rows));
        for (int i = 1; i < sz(rows); i += 2) res[i] = so[i / 2];
        for (int i = 0, k = 0; i < sz(rows); i += 2) {
            int hi = i + 1 < sz(rows) ? res[i + 1] : st.back();
            res[i] = st[k];
            while (st[k] != hi) { k++; if (A(rows[i], st[k]) < A(rows[i], res[i])) res[i] = st[k]; }
        }
        return res;
    };
    vector<int> r(n), c(m); iota(all(r), 0); iota(all(c), 0);
    return rec(rec, r, c);
}

Online case (row ‘s entries depend on values computed from earlier rows): LARSCH achieves but is intricate. Simpler: CDQ over the index — compute the left half recursively, then the left half’s decisions are all known, so their contribution to the right half is an offline row-minima problem (SMAWK or the D&C of D&C Optimization), then recurse on the right half: or .

SMAWK’s real-world reach: Knuth–Plass line/paragraph breaking (least-weight subsequence with a concave cost), all-pairs shortest paths restricted to Monge graphs, RNA secondary-structure prediction, largest empty rectangle, and optimal BSTs whose cost happens to be Monge.

Which one

ShapeUse
, window bounds monotonedeque,
convex hull trick (CHT)
Monge (QI), online1D1D binary search,
Monge, layeredD&C optimisation or SMAWK per layer
Monge, all row minima known in advanceSMAWK,
min-plus convolution with a convex sequenceSMAWK / D&C (Knapsack)