Back to Data Structures

Slope Trick

October 17, 2026 Wasil Zafar 19 min read

Concepts, invariants, and Python, C++, and Java implementations of Slope Trick

Table of Contents

  1. Why Slope Trick Matters
  2. Core Concepts and Terminology
  3. How Slope Trick Works, Step by Step
  4. Worked Example with a Full Trace
  5. Implementation in Python, C++, and Java
  6. Complexity, Correctness, and Invariants
  7. Variants, Pitfalls, and Optimizations
  8. Practice Problems and Real-World Use

Why Slope Trick Matters

Dynamic programming problems over continuous domain variables or large integer ranges often involve state transitions of the form $dp_i(x) = \min_y \{ dp_{i-1}(y) + \text{cost}(x, y) \}$. When the domain of $x$ is large (e.g., $10^9$), standard array-based dynamic programming fails due to memory and time constraints. Even coordinate compression becomes unusable when the transition cost function depends dynamically on continuous distance metrics, such as $ x - a_i|$.

Slope Trick is a data structure technique that optimizes dynamic programming algorithms where the state function $f(x)$ is continuous, piecewise-linear, and convex. Instead of evaluating $f(x)$ at discrete points, Slope Trick maintains $f(x)$ implicitly by tracking its minimum value $min\_f$ alongside two multisets of "knots" (change points) where the slope of $f(x)$ increases or decreases by $1$. Using max-heaps and min-heaps to represent these knots reduces complex functional transitions—such as prefix minima sweeps and function additions—to $O(\log N)$ priority queue operations.

Slope Trick represents a convex piecewise-linear function $f(x)$ using its minimum value $min\_f$ and the multiset of $x$-coordinates where its derivative changes. By managing these transition points in max- and min-priority queues, DP transitions like $f(x) \leftarrow \min_{y \le x} f(y) + |x - a_i|$ are computed in $O(\log N)$ time per step, turning $O(N^2)$ or $O(N \cdot \text{Range})$ DP problems into $O(N \log N)$ execution time.

Core Concepts and Terminology

A continuous function $f: \mathbb{R} \to \mathbb{R}$ is piecewise-linear if its domain can be partitioned into intervals over which $f(x)$ is a line segment $a x + b$. It is convex if its derivative $f'(x)$ is non-decreasing across its domain. The points where $f'(x)$ changes discretely are called knots (or slope change points).

Because $f'(x)$ changes only by integer increments in typical competitive programming and optimization tasks, any convex piecewise-linear function $f(x)$ can be uniquely identified by:

  1. The minimum value of the function, $min\_f = \min_x f(x)$.
  2. The interval $[L_{max}, R_{min}]$ where $f(x) = min\_f$ (the flat bottom where slope is $0$).
  3. A max-heap $L$ storing all knots to the left of $L_{max}$ (where slope changes by $+1$ as we move right).
  4. A min-heap $R$ storing all knots to the right of $R_{min}$ (where slope changes by $+1$ as we move right).

Formally, the function is expressed as:

$$f(x) = min\_f + \sum_{l \in L} \max(0, l - x) + \sum_{r \in R} \max(0, x - r)$$

Anatomy of a convex piecewise-linear function with left and right knots A convex piecewise-linear function descends with slope minus two, flattens to slope minus one, then runs flat at its minimum value between the knots L max and R min, and rises with slopes plus one and plus two past the right knots. Orange dots mark the four knots, and a shaded band highlights the flat minimum region. Anatomy of a Convex Piecewise-Linear Function x f(x) min_f l1 L_max (l2) R_min (r1) r2 slope = −2 slope = −1 slope = 0 slope = +1 slope = +2 Each orange knot adds +1 to the slope as x moves right; two left knots and two right knots bracket the flat bottom. f(x) = min_f + Σ max(0, l − x) + Σ max(0, x − r) — stored implicitly as min_f, heap L, and heap R.
TermDefinitionRole in Slope TrickExample
Knot (Change Point)An $x$-coordinate where $f'(x)$ discretely increases by $+1$.Inserted into $L$ or $R$ to change function steepness.At $x = 3$, slope transitions from $0$ to $+1$.
Left Heap ($L$)Max-priority queue storing all knots $\le L_{max}$.Top element $L.top()$ equals $L_{max}$, the left boundary of minimum.$L = \{1, 3, 4\}$, $L.top() = 4$.
Right Heap ($R$)Min-priority queue storing all knots $\ge R_{min}$.Top element $R.top()$ equals $R_{min}$, the right boundary of minimum.$R = \{6, 8\}$, $R.top() = 6$.
Minimum Value ($min\_f$)The baseline minimum cost $\min_x f(x)$.Updated when adding functions or modifying heap roots.$min\_f = 0$ initially.
Prefix MinimumOperation $f_{new}(x) = \min_{y \le x} f_{old}(y)$.Flattens function to the right of $L_{max}$; clears $R$.$f(x)$ becomes constant for all $x \ge L_{max}$.
Shift / TranslationOperation $f_{new}(x) = f_{old}(x - c)$.Shifts knot coordinates in $L$ and $R$ by $+c$.Adding lazy offset $c$ to heap elements.

How Slope Trick Works, Step by Step

Slope Trick maintains a convex function under three primary operations:

1. Adding an Absolute Distance Function $g(x) = \vert{}x - a\vert{}$

Adding $\vert{}x - a\vert{}$ increases the slope by $+1$ for $x > a$ and decreases the slope by $-1$ for $x \lt a$ (which is equivalent to adding a knot at $x = a$ to both $L$ and $R$).

  • Case 1 ($a \lt L_{max}$): The point $a$ falls in the left region. $min\_f$ increases by $L_{max} - a$. The old $L_{max}$ is pushed to $R$, and $a$ is added twice to $L$ (so $L.top()$ becomes $a$).
  • Case 2 ($a > R_{min}$): The point $a$ falls in the right region. $min\_f$ increases by $a - R_{min}$. The old $R_{min}$ is pushed to $L$, and $a$ is added twice to $R$.
  • Case 3 ($L_{max} \le a \le R_{min}$): The point $a$ falls inside the minimum range. $min\_f$ remains unchanged. $a$ is added once to $L$ and once to $R$.

2. Prefix Minimum Sweep $f_{new}(x) = \min_{y \le x} f_{old}(y)$

This operation is required when enforcing non-decreasing sequence constraints ($x_i \ge x_{i-1}$).

  • For all $x \ge L_{max}$, $f_{new}(x) = min\_f$ (the slope stays $0$).
  • All knots to the right of $L_{max}$ are removed, meaning Right Heap $R$ is completely cleared.
Prefix minimum sweep flattens the function and clears heap R Before the sweep the convex function keeps a flat bottom between L max and R min plus a rising right branch tracked by heap R. After the prefix minimum the function stays at its minimum value for every x beyond L max, the old rising knots are crossed out, and heap R is cleared. Prefix Minimum Sweep: Clearing the Right Heap BEFORE — BOTH HEAPS ACTIVE L_max R_min Right heap R = {R_min, ...} active. f keeps its rising right branch. AFTER — PREFIX MINIMUM old R knots removed L_max Flat minimum extended to +∞ (slope = 0) Action: Right heap R is cleared! R = {} The sweep keeps every value left of L_max and freezes f at min_f for all x ≥ L_max. Clearing R discards the rising knots — never again optimal under a non-decreasing constraint.

The control flow for updating a non-decreasing sequence DP ($dp_i(x) = \min_{y \le x} dp_{i-1}(y) + \vert{}x - a_i\vert{}$) follows this decision process:

Slope Trick control flow
flowchart TD
A[Start: Next Element a_i] --> B{Check a_i vs L_max}
B -- a_i < L_max --C[Cost Increase: min_f += L_max - a_i]
C --> D[Pop L_max from Left Heap L]
D --> E[Push a_i into Left Heap L twice]
B -- a_i >= L_max --> F[No Cost Increase: min_f unchanged]
F --> G[Push a_i into Left Heap L once]
E --> H[Apply Prefix Minimum: Clear Right Heap R]
G --> H
H --> I[End: State updated for a_i]

Worked Example with a Full Trace

Consider the classic problem: Make Array Non-Decreasing with Minimum Total Absolute Modification (Codeforces 713C / LeetCode 2263).

Given array $A = [4, 1, 3, 2]$, we want to construct a non-decreasing array $B$ ($B_0 \le B_1 \le B_2 \le B_3$) minimizing $\sum_{i=0}^3 \vert{}A_i - B_i\vert{}$.

We initialize $min\_f = 0$ and an empty max-heap $L$.

Slope trick trace for the sample array four, one, three, two Five trace steps. Steps where the new element falls below the heap top pay the difference into the minimum cost and push the element twice; all other steps push once with no cost. The final minimum modification cost is four. Slope Trick Trace for A = [4, 1, 3, 2] Step A[i] L.top() (L_max) Condition & Action min_f Heap L (Max) 0 Init - Start empty 0 {} 1 4 None First element → Push 4 0 {4} 2 1 4 1 < 4 → min_f += 4−1=3; Pop 4; Push 1 twice 3 {1, 1} 3 3 1 3 ≥ 1 → min_f unchanged; Push 3 once 3 {3, 1, 1} 4 2 3 2 < 3 → min_f += 3−2=1; Pop 3; Push 2 twice 4 {2, 2, 1, 1} Each element below the current L_max pays (L_max − x) into min_f and re-anchors the heap at x. Final min_f = 4 — matching B = [1, 1, 2, 2] with cost 3 + 0 + 1 + 0 = 4.
Worked Example

Trace It by Hand

Question: Given array $A = [4, 1, 3, 2]$, what is the final minimum cost to make $A$ non-decreasing? How does the contents of the max-heap $L$ relate to the optimal sequence $B$?

Step 1: Trace $min\_f$:

  • $i=0, A[0]=4$: $L = \{4\}$, $min\_f = 0$.
  • $i=1, A[1]=1$: $1 \lt L.top()=4 \implies min\_f += 4 - 1 = 3$. $L$ pops $4$ and inserts $1$ twice $\implies L = \{1, 1\}$.
  • $i=2, A[2]=3$: $3 \ge L.top()=1 \implies min\_f$ stays $3$. $L$ inserts $3 \implies L = \{3, 1, 1\}$.
  • $i=3, A[3]=2$: $2 \lt L.top()=3 \implies min\_f += 3 - 2 = 1$ (Total $min\_f = 4$). $L$ pops $3$ and inserts $2$ twice $\implies L = \{2, 2, 1, 1\}$.

Step 2: Final Minimum Modification Cost = $min\_f = 4$.

Step 3: Reconstruction of Optimal Non-Decreasing Sequence $B$: Working backwards from the max-heap states yields $B = [2, 2, 2, 2]$ or $B = [1, 1, 2, 2]$ (both achieve modification cost $\vert{}4-1\vert{} + \vert{}1-1\vert{} + \vert{}3-2\vert{} + \vert{}2-2\vert{} = 3 + 0 + 1 + 0 = 4$).

Implementation in Python, C++, and Java

The following implementations compute the minimum cost to make an array non-decreasing using Slope Trick. All three programs feature identical logic, process the sample input $A = [4, 1, 3, 2]$, and produce identical terminal outputs.

import heapq

def solve_slope_trick(arr):
    # Max-heap L stored using negated values
    L = []
    min_f = 0

    for idx, x in enumerate(arr):
        if L and -L[0] > x:
            l_max = -heapq.heappop(L)
            min_f += l_max - x
            heapq.heappush(L, -x)
            heapq.heappush(L, -x)
        else:
            heapq.heappush(L, -x)

        print(f"Processing x = {x}: min_cost = {min_f}, heap top = {-L[0]}")

    print(f"\nFinal Minimum Cost: {min_f}")
    return min_f

if __name__ == "__main__":
    arr = [4, 1, 3, 2]
    solve_slope_trick(arr)
#include <iostream>
#include <vector>
#include <queue>

long long solve_slope_trick(const std::vector<long long>& arr) {
    // Max-heap L
    std::priority_queue<long long> L;
    long long min_f = 0;

    for (long long x : arr) {
        if (!L.empty() && L.top() > x) {
            long long l_max = L.top();
            L.pop();
            min_f += l_max - x;
            L.push(x);
            L.push(x);
        } else {
            L.push(x);
        }

        std::cout << "Processing x = " << x << ": min_cost = " << min_f
                  << ", heap top = " << L.top() << "\n";
    }

    std::cout << "\nFinal Minimum Cost: " << min_f << "\n";
    return min_f;
}

int main() {
    std::vector<long long> arr = {4, 1, 3, 2};
    solve_slope_trick(arr);
    return 0;
}
import java.util.Collections;
import java.util.PriorityQueue;

public class Main {
    public static long solveSlopeTrick(long[] arr) {
        // Max-heap L
        PriorityQueue<Long> L = new PriorityQueue<>(Collections.reverseOrder());
        long minF = 0;

        for (long x : arr) {
            if (!L.isEmpty() && L.peek() > x) {
                long lMax = L.poll();
                minF += lMax - x;
                L.add(x);
                L.add(x);
            } else {
                L.add(x);
            }

            System.out.println("Processing x = " + x + ": min_cost = " + minF
                               + ", heap top = " + L.peek());
        }

        System.out.println("\nFinal Minimum Cost: " + minF);
        return minF;
    }

    public static void main(String[] args) {
        long[] arr = {4, 1, 3, 2};
        solveSlopeTrick(arr);
    }
}

Complexity, Correctness, and Invariants

The time and space bounds of Slope Trick depend on priority queue operations over $N$ elements.

Algorithm / TechniqueTime ComplexityAuxiliary SpaceKey Assumptions / Domain
Slope Trick$O(N \log N)$$O(N)$DP functions must be continuous, piecewise-linear, and convex.
Coordinate Compression + 2D DP$O(N^2)$$O(N)$Discrete states; applicable to non-convex continuous costs.
Segment Tree + Coordinate Compression$O(N \log N)$$O(N)$Requires discretization of domain prior to tree construction.
Divide and Conquer DP / SMAWK$O(N \log N)$ or $O(N)$$O(N)$Monotonicity of decision points; rigid DP grid structure.

Variants, Pitfalls, and Optimizations

Slope Trick extends to several functional operations:

  1. Shift / Translation $f_{new}(x) = \min_{x - b \le y \le x - a} f_{old}(y)$:

Used in inventory smoothing or bounded movement problems. This shifts all knots in $L$ by $+a$ and all knots in $R$ by $+b$. Rather than modifying every heap element, maintain lazy offset tags shift_L and shift_R. 2. Both Left and Right Slopes: When prefix minimum is not applied, $R$ is preserved. Operations require checking both $L.top()$ and $R.top()$ to determine where $a_i$ lies relative to $[L_{max}, R_{min}]$.

Variants: shifted windows and two-sided hulls The translation variant slides the valid y window, shifting left knots by a and right knots by b, done in constant time with lazy offset tags. The two-sided variant keeps both heaps when no prefix minimum is applied, comparing each new point against the interval between the two heap tops. Variants: Shifted Windows and Two-Sided Hulls TRANSLATION / SHIFT f(x) ← min over y in [x−b, x−a] Shifts knots: L by +a, R by +b Lazy tags shift_L, shift_R avoid touching every heap element True knot = stored value + tag applied on push and on pop O(1) amortized per translation. BOTH LEFT AND RIGHT SLOPES No prefix-min: heap R survives Compare a_i to [L_max, R_min] Three cases steer cost updates min_f may grow on either side Kept when order is unconstrained or moves are range-bounded Two heaps track the full hull. Lazy offsets shift whole heaps in O(1); the real knot position is stored value plus tag. With both heaps intact, f(x) keeps its rising right branch — ready for range-constrained moves.

Common Implementation Pitfalls:

  1. Pushing $a_i$ Only Once for $\vert{}x - a_i\vert{}$: The function $\vert{}x - a_i\vert{}$ has slope $-1$ for $x \lt a_i$ and $+1$ for $x > a_i$. The slope change at $a_i$ is $(+1) - (-1) = 2$. You must push $a_i$ twice into the heap when processing absolute distance functions.
  2. Integer Overflow in $min\_f$: Accumulating $min\_f += L.top() - x$ can exceed 32-bit signed integer limits ($2^{31}-1$) when $N = 10^5$ and $A_i = 10^9$. Always use 64-bit integers (long long in C++, long in Java).
  3. Strictly Increasing Sequence Transformation: To force $B_0 \lt B_1 \lt \dots \lt B_{N-1}$, transform the input array to $A_i' = A_i - i$ and solve the non-decreasing problem on $A'$.

Practice Problems and Real-World Use

Slope Trick appears in competitive programming and production optimization tasks involving continuous inventory planning and non-decreasing transformations.

Problem / SystemCore Concept TestedDifficulty
Codeforces 713C - SequenceConvert array to strictly increasing sequence with minimum modifications.Hard
AtCoder ABC 217 H - Snuke's Panic2-heap Slope Trick with lazy shift offsets ($f(x \leftarrow \min_{x-dt \le y \le x+dt} f(y))$).Hard
LeetCode 2263 - Make Array Non-DecreasingBasic Slope Trick using single max-heap.Medium-Hard
Codeforces 1534F - Falling SandSlope Trick on tree DP transitions.Very Hard
Supply Chain Inventory ControlSmooth warehouse order volumes over continuous time steps under storage penalties.Real-World System
Time-Series Signal DenoisingFit piecewise non-decreasing trends to noisy continuous sensor streams.Real-World System