Exchange Arguments in DP
Key Points
- •An exchange argument proves that any optimal solution can be reordered to satisfy a simple sorting rule by showing that swapping adjacent out-of-order elements never helps.
- •Once a correct order is proven, dynamic programming (DP) can run along that order to choose a subset or make decisions with much smaller state space.
- •Classic scheduling rules like Smith’s Rule (sort by /) and Earliest Due Date (EDD) follow from adjacent-swap comparisons.
- •For weighted interval scheduling, sorting by finish time and using binary search for compatibility enables a clean 1D DP.
- •In objectives like sum with optional skipping, exchange arguments fix the sequencing; then a knapsack-style DP decides which jobs to keep.
- •This technique often reduces a combinatorial explosion over permutations to O(n), O(n log n), or O(nT) DP on a single index or time dimension.
- •Always write the two-job swap inequality and derive a simple sort key; that key is the backbone of the DP’s transition order.
- •Beware of ties and equalities; define a stable tiebreaker to keep DP transitions correct and avoid off-by-one feasibility issues.
Prerequisites
- →Sorting and order statistics — Exchange arguments depend on defining and applying a correct sort order, including stable tiebreaks.
- →Dynamic programming fundamentals — You need to formulate states, transitions, and rolling arrays to exploit the fixed order.
- →Knapsack DP pattern — Many post-order DPs are capacity/time-based and mirror 0/1 knapsack updates.
- →Binary search — Used to compute predecessor indices p(i) efficiently after sorting (e.g., intervals).
- →Scheduling objectives — Understanding completion time and deadlines helps derive the swap inequalities.
- →Asymptotic analysis — To compare factorial search vs. sort + DP and to reason about pseudo-polynomial DPs.
- →Integer arithmetic robustness — Avoid floating-point when comparing ratios like p_i/w_i; use cross-multiplication.
Detailed Explanation
Tap terms for definitions01Overview
Exchange arguments in DP are a powerful way to tame problems that seem to require searching over many permutations. The core idea is to reason locally about two adjacent elements in a candidate solution (for example, two adjacent jobs in a schedule). If swapping any inverted pair (i then j when the objective prefers j then i) never makes the answer better, then there exists an optimal solution that is globally sorted by a simple key. This turns a scary global search over n! orders into a simple sort followed by a dynamic program on a single sequence. Once the correct order is established, the DP can concentrate on selection or resource allocation decisions (e.g., which jobs to keep under deadlines or capacity), often reducing states from two dimensions (order plus resource) to one (resource over a fixed order). This idea underlies many classical scheduling rules such as Smith’s Rule for minimizing the weighted sum of completion times, and EDD (Earliest Due Date) order. It also appears in interval problems where sorting by finish times makes compatibility structure one-dimensional. By combining exchange arguments with DP, we create algorithms that are both provably correct and efficient, transforming problems from exponential to polynomial time via a sort and a linear or near-linear DP scan.
02Intuition & Analogies
Imagine arranging books on a shelf so that taking them off later is as convenient as possible. You might not know the perfect order at first, but you can test local improvements: if two adjacent books look “out of order” by your rule (say, heavier ones should go later), swap them. If such a swap never makes things worse, you can keep swapping until the entire shelf obeys the rule; the final shelf is then as good as any best arrangement. That’s an exchange argument: local, adjacent changes enforce a global property. In algorithms, our “books” are jobs, intervals, or tasks. The rule can be a simple key like ratio p_i/w_i, a deadline d_i, or a finish time f_i. The beauty is that you don’t need to explore all permutations. You only prove once that any inverted adjacent pair can be fixed by swapping without hurting the objective. After that, sort once, lock the order, and move on. Where does DP come in? Once the order is fixed, you often still need to decide which items to include, how to use a capacity like time, or how to combine compatible pieces. With the order pinned down, these decisions can be made by sweeping from left to right and keeping a compact state—like the best value achievable by time t. Without the exchange argument, the DP would have to carry the order as a state, exploding to intractable size. With it, your DP becomes a simple, fast pass over a sorted list, just like processing books on a neatly ordered shelf.
03Formal Definition
04When to Use
Use exchange arguments when your problem involves arranging or sequencing items and the objective decomposes locally enough to compare adjacent pairs. Hallmarks include: (1) scheduling metrics that depend on completion times or deadlines, (2) interval or segment choices where ordering by a timestamp simplifies compatibility, and (3) DP formulations that seem to require carrying a permutation as state. Common patterns: (a) Minimizing (\sum w_i C_i): Smith’s Rule proves sorting by (p_i/w_i) is optimal for any fixed subset; then use DP to decide which jobs to include under penalties or capacity. (b) Maximizing on-time jobs with deadlines: EDD order is optimal among on-time schedules; DP then chooses the best feasible subset by time. (c) Weighted interval scheduling: sorting by finish time allows computing the predecessor function (p(i)) and a simple 1D DP recurrence. (d) Two-machine flow shop (Johnson’s Rule): a different swap inequality yields an order reducing a more complex optimization to a linear pass or DP on that order. If you can write a clear two-item inequality that yields a total preorder and your objective combines linearly or by prefix sums, exchange arguments are likely to pay off. If the objective is highly nonlocal or constraints couple far-apart positions (e.g., complex precedence), exchange arguments may not apply directly.
⚠️Common Mistakes
• Skipping the explicit two-item analysis: You must compute the objective difference for (i before j) versus (j before i) and derive the ordering condition. Hand-waving about an order can lead to wrong sorts and incorrect DPs. • Ignoring ties: When the inequality is non-strict, you must define a stable tiebreaker (e.g., by index) to keep transitions deterministic, especially if the DP depends on prefix feasibility. • Applying the order outside its assumptions: Smith’s Rule (sort by p_i/w_i) holds for minimizing (\sum w_i C_i) on a single machine without idle time; changing the model (release times, machine environments) can invalidate the exchange argument. • Mixing selection and order in DP states: After proving the order, the DP should not re-encode permutations. Use prefix-based transitions; otherwise, you lose the benefit and blow up complexity. • Double-counting contributions: For objectives like (\sum w_i C_i), ensure the DP adds the correct marginal cost when placing a job at total time t (e.g., add (w_i (t+p_i))). Incorrect incremental accounting is a common bug. • Capacity bounds too large: Knapsack-like DPs after sorting often depend on total processing time T. If T is large, consider pruning, scaling, or alternative approaches; the exchange argument does not fix pseudo-polynomial dependence. • Wrong compatibility index: In interval scheduling DPs, computing (p(i)) (the last non-overlapping interval) incorrectly or without stable sorting leads to incorrect transitions.
Key Formulas
Swap Difference
Explanation: Compute the change in objective when swapping adjacent items i and j. If \( 0\) for every inverted pair, then sorting by the implied key does not worsen the solution.
Smith's Rule Inequality
Explanation: For minimizing \( \), placing i before j is better when \( \). This yields the order by nondecreasing \(/\).
Weighted Interval DP
Explanation: After sorting by finish time, the best weight up to interval i is either taking i plus the best compatible prefix p(i), or skipping i. This 1D DP runs in index order.
Smith + Subset Transition
Explanation: With jobs sorted by \(/\), placing job i at cumulative time t adds cost \( (t+)\). A knapsack-like DP over time t chooses which jobs to include.
Skip Penalty Transition
Explanation: If job i can be skipped with penalty \(\), update the DP for staying at the same time t and paying \(\).
EDD On-time Selection
Explanation: After sorting by due dates, we may place job i to finish by \(\); if so, increase the on-time weight. Otherwise, we cannot place it at time t.
Sort + Binary Search + DP
Explanation: Many exchange-argument DPs sort once, precompute compatibilities via binary search, and then do a linear DP, yielding total \(O(n n)\) time.
Weighted Completion Objective
Explanation: A common scheduling objective. After ordering by \(/\), this sum can be accumulated incrementally as the DP advances time.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 /* 4 Problem: Given n intervals [s_i, f_i) with values v_i, choose a maximum-weight set of non-overlapping intervals. 5 Technique: Sort by finish time (exchange-style reasoning shows this order suffices), 6 precompute p(i): last non-overlapping interval before i, then 1D DP. 7 Input format: 8 n 9 s_1 f_1 v_1 10 ... 11 s_n f_n v_n 12 */ 13 struct Interval { long long s, f, v; }; 14 int main(){ 15 ios::sync_with_stdio(false); 16 cin.tie(nullptr); 17 18 int n; if(!(cin >> n)) return 0; 19 vector<Interval> a(n); 20 for(int i=0;i<n;++i) cin >> a[i].s >> a[i].f >> a[i].v; 21 22 sort(a.begin(), a.end(), [](const Interval& A, const Interval& B){ 23 if (A.f != B.f) return A.f < B.f; // sort by finish time 24 return A.s < B.s; // stable-ish tiebreaker 25 }); 26 27 // Build arrays of starts and finishes for binary search 28 vector<long long> finish(n), start(n); 29 for(int i=0;i<n;++i){ finish[i] = a[i].f; start[i] = a[i].s; } 30 31 // p[i]: rightmost index < i with finish <= start[i] 32 vector<int> p(n, -1); 33 for(int i=0;i<n;++i){ 34 int j = int(upper_bound(finish.begin(), finish.end(), start[i]) - finish.begin()) - 1; 35 p[i] = j; // could be -1 if none 36 } 37 38 // dp[i]: best value using intervals up to index i (1-based for clarity) 39 vector<long long> dp(n+1, 0); 40 for(int i=1;i<=n;++i){ 41 long long take = a[i-1].v + (p[i-1] >= 0 ? dp[p[i-1]+1] : 0LL); 42 long long skip = dp[i-1]; 43 dp[i] = max(take, skip); 44 } 45 cout << dp[n] << "\n"; 46 return 0; 47 } 48
We sort intervals by finish time so compatibility becomes a simple predecessor relation p(i). The DP state dp[i] is the optimal weight using the first i intervals in finish-time order. Transition: either take interval i (and jump to p(i)) or skip it. The exchange-style justification is that ordering by finish time linearizes compatibility and there exists an optimal solution consistent with this order.
1 #include <bits/stdc++.h> 2 using namespace std; 3 /* 4 Problem: Each job i has processing time p_i, weight w_i (contributes w_i * C_i if scheduled), 5 and skip penalty s_i if not scheduled. Schedule a subset (no idle time among scheduled) 6 to minimize: sum_{scheduled} w_i * C_i + sum_{skipped} s_i. 7 8 Exchange Argument: For any fixed subset, the optimal order is by nondecreasing p_i / w_i (Smith's Rule). 9 Proof via adjacent swap: i before j is better iff w_j p_i <= w_i p_j. 10 11 DP: After sorting by p_i/w_i, use knapsack-like DP over total time t. 12 dp[t] = minimum cost achievable with total scheduled time exactly t over processed prefix. 13 Transition on job i: 14 - Skip: dp'[t] = min(dp'[t], dp[t] + s_i) 15 - Take: dp'[t+p_i] = min(dp'[t+p_i], dp[t] + w_i * (t + p_i)) 16 Answer = min_t dp[t]. 17 18 Input format: 19 n 20 p_1 w_1 s_1 21 ... 22 p_n w_n s_n 23 */ 24 struct Job { int p; long long w; long long s; int id; }; 25 int main(){ 26 ios::sync_with_stdio(false); 27 cin.tie(nullptr); 28 29 int n; if(!(cin >> n)) return 0; 30 vector<Job> a(n); 31 long long sumP = 0; 32 for(int i=0;i<n;++i){ cin >> a[i].p >> a[i].w >> a[i].s; a[i].id=i; sumP += a[i].p; } 33 34 // Sort by nondecreasing p_i / w_i using cross-multiplication to avoid precision. 35 stable_sort(a.begin(), a.end(), [](const Job& A, const Job& B){ 36 __int128 lhs = (__int128)A.p * B.w; 37 __int128 rhs = (__int128)B.p * A.w; 38 if (lhs != rhs) return lhs < rhs; // smaller ratio first 39 return A.id < B.id; // stable tiebreaker 40 }); 41 42 const long long INF = (1LL<<62); 43 int T = (int)sumP; 44 vector<long long> dp(T+1, INF), ndp(T+1, INF); 45 dp[0] = 0; 46 for(const auto& job : a){ 47 fill(ndp.begin(), ndp.end(), INF); 48 for(int t=0;t<=T;++t){ 49 if(dp[t] == INF) continue; 50 // Skip this job: pay penalty, time unchanged 51 ndp[t] = min(ndp[t], dp[t] + job.s); 52 // Take this job: increase time and pay marginal cost w_i * (t + p_i) 53 if (t + job.p <= T) { 54 long long cost = dp[t] + job.w * (long long)(t + job.p); 55 ndp[t + job.p] = min(ndp[t + job.p], cost); 56 } 57 } 58 dp.swap(ndp); 59 } 60 61 long long ans = INF; 62 for(int t=0;t<=T;++t) ans = min(ans, dp[t]); 63 cout << ans << "\n"; 64 return 0; 65 } 66
We first prove the sequencing by Smith’s Rule using an adjacent-swap inequality; this removes permutations from consideration. Then the only remaining choice is which jobs to include. The DP runs over cumulative time t and updates costs when skipping (add s_i) or taking (add w_i times the completion time t+p_i). Rolling arrays keep memory O(T), where T is the total processing time.
1 #include <bits/stdc++.h> 2 using namespace std; 3 /* 4 Problem: Each job i has processing time p_i, due date d_i, and weight w_i. 5 Choose a subset and an order so that chosen jobs finish by their own deadlines, maximizing sum of w_i. 6 7 Exchange Argument: Among all on-time schedules of a fixed subset, an EDD (nondecreasing d_i) order is optimal. 8 If two jobs are out of EDD order and one becomes late, swapping cannot hurt feasibility and may help. 9 10 DP after sorting by d_i: 11 dp[t] = maximum weight achievable finishing at total time exactly t (t is cumulative processing time). 12 Transition on job i (in EDD order): 13 - Skip: dp'[t] = max(dp'[t], dp[t]) 14 - Take on time: if t + p_i <= d_i then dp'[t+p_i] = max(dp'[t+p_i], dp[t] + w_i) 15 Answer = max_t dp[t]. 16 17 Input format: 18 n 19 p_1 d_1 w_1 20 ... 21 p_n d_n w_n 22 */ 23 struct J { int p; int d; long long w; int id; }; 24 int main(){ 25 ios::sync_with_stdio(false); 26 cin.tie(nullptr); 27 28 int n; if(!(cin >> n)) return 0; 29 vector<J> a(n); 30 long long sumP = 0; 31 for(int i=0;i<n;++i){ cin >> a[i].p >> a[i].d >> a[i].w; a[i].id=i; sumP += a[i].p; } 32 33 stable_sort(a.begin(), a.end(), [](const J& A, const J& B){ 34 if (A.d != B.d) return A.d < B.d; // EDD primary 35 return A.id < B.id; // stable tiebreaker 36 }); 37 38 int T = (int)sumP; 39 const long long NEG = -(1LL<<60); 40 vector<long long> dp(T+1, NEG), ndp(T+1, NEG); 41 dp[0] = 0; 42 43 for(const auto& job : a){ 44 fill(ndp.begin(), ndp.end(), NEG); 45 for(int t=0;t<=T;++t){ 46 if (dp[t] == NEG) continue; 47 // Skip job 48 ndp[t] = max(ndp[t], dp[t]); 49 // Take job if it completes on time in EDD prefix 50 if (t + job.p <= job.d) { 51 ndp[t + job.p] = max(ndp[t + job.p], dp[t] + job.w); 52 } 53 } 54 dp.swap(ndp); 55 } 56 57 long long ans = 0; 58 for(int t=0;t<=T;++t) ans = max(ans, dp[t]); 59 cout << ans << "\n"; 60 return 0; 61 } 62
The EDD exchange argument asserts that, for any subset that can all meet their deadlines, sorting by due date keeps feasibility and does not reduce the achievable total weight. With the order fixed, we run a knapsack-like DP over cumulative time, only allowing placements that still finish before each job’s deadline. The maximum over all times is the best on-time total weight.