14.1. Rod Cutting
This section completes the fourth-edition §14.1 algorithm boundary for rod
cutting on top of the legacy recurrence and bottom-up table
(CLRSLean.Chapter_15.Section_15_1_Rod_Cutting). It adds the optimal-cut
reconstruction of EXTENDED-BOTTOM-UP-CUT-ROD, the top-down
MEMOIZED-CUT-ROD cache-threading algorithm, and the explicit O(n²) step
count of BOTTOM-UP-CUT-ROD.
Main results:
-
Definition
rodCutFirstCutand theoremsrodCutFirstCut_mem,rodCutFirstCut_le,rodCutFirstCut_value: the optimal first cutsofEXTENDED-BOTTOM-UP-CUT-RODis a valid cut in1 .. nand attains the recurrence maximum. -
Definition
rodCutPlanand theoremsrodCutPlan_correct,rodCutPlan_optimal:PRINT-CUT-ROD-SOLUTIONrebuilds an optimal cutting plan of lengthn. -
CLRS.Chapter15.RodExecution.executefills an array using a single stored prefix per outer iteration.rodExecution_candidates_eqconnects its candidate counter torodCutStepCount;rodExecution_candidates_le_quadraticbounds those executed candidate visits. Price lookup, array access, and arithmetic are primitive events; their internals are excluded. -
Definition
memoizedRodCutand theoremsmemoizedRodCut_value,memoizedRodCut_correct: the top-downMEMOIZED-CUT-RODcache-threading algorithm returns the optimal revenue and keeps its cache consistent.
Status: proved for cut reconstruction, top-down memoization, and the O(n²)
step-count cost analysis. The mutable-array bottom-up refinement and the Bellman
recurrence remain in the legacy source.
Notation conventions used in this section:
-
price: the price tableprice n= revenue of an uncut piece of lengthn -
n: the rod length
namespace CLRSnamespace Chapter15Optimal-cut reconstruction (CLRS §14.1 EXTENDED-BOTTOM-UP-CUT-ROD)
The candidate first cuts for a rod of length n, listed in increasing order
as [1, 2, ..., n].
def rodCutCandidates (n : Nat) : List Nat :=
(List.range n).map (fun i => i + 1)
Membership in the candidate list is exactly a valid first cut in 1 .. n.
theorem mem_rodCutCandidates (n cut : Nat) :
cut ∈ rodCutCandidates n ↔ cut ∈ Finset.Icc 1 n := by
simp [rodCutCandidates, List.mem_map, List.mem_range, Finset.mem_Icc]
constructor
· rintro ⟨i, hi, rfl⟩
omega
· intro h
refine ⟨cut - 1, ?_, ?_⟩
· omega
· omegaPick the first cut with the larger first-cut revenue, breaking ties toward the smaller cut.
def betterRodCut (price : Nat → Nat) (n : Nat) (a b : Nat) : Nat :=
if FirstCutValue price (bottomUpRodRevenue price) n a <
FirstCutValue price (bottomUpRodRevenue price) n b then b else a
The best first cut among a list of candidate cuts, or none for an empty
list.
def bestRodCutOf (price : Nat → Nat) (n : Nat) : List Nat → Option Nat
| [] => none
| cut :: rest =>
match bestRodCutOf price n rest with
| none => some cut
| some best => some (betterRodCut price n cut best)The list-based selector returns an element of the candidate list whose first-cut revenue is at least every candidate's.
theorem bestRodCutOf_correct (price : Nat → Nat) (n : Nat) {best : Nat} {cuts : List Nat}
(hbest : bestRodCutOf price n cuts = some best) :
best ∈ cuts ∧
∀ cut, cut ∈ cuts →
FirstCutValue price (bottomUpRodRevenue price) n cut ≤
FirstCutValue price (bottomUpRodRevenue price) n best := by
induction cuts generalizing best with
| nil =>
simp [bestRodCutOf] at hbest
| cons cut rest ih =>
simp [bestRodCutOf] at hbest
cases hrest : bestRodCutOf price n rest with
| none =>
have hrestNil : rest = [] := by
cases rest with
| nil => rfl
| cons restCut restTail =>
cases htail : bestRodCutOf price n restTail <;>
simp [bestRodCutOf, htail] at hrest
simp [hrest] at hbest
subst best
subst rest
constructor
· simp
· intro other hother
simp at hother
subst other
exact le_rfl
| some restBest =>
simp [hrest] at hbest
have hrestCorrect := ih hrest
by_cases hlt : FirstCutValue price (bottomUpRodRevenue price) n cut <
FirstCutValue price (bottomUpRodRevenue price) n restBest
· simp [betterRodCut, hlt] at hbest
subst best
constructor
· simp [hrestCorrect.1]
· intro other hother
simp at hother
rcases hother with hsame | hinRest
· subst other
exact le_of_lt hlt
· exact hrestCorrect.2 other hinRest
· simp [betterRodCut, hlt] at hbest
subst best
constructor
· simp
· intro other hother
simp at hother
rcases hother with hsame | hinRest
· subst other
exact le_rfl
· exact le_trans (hrestCorrect.2 other hinRest) (le_of_not_gt hlt)Every nonempty candidate list has a selected best first cut.
theorem bestRodCutOf_exists_of_ne_nil (price : Nat → Nat) (n : Nat) {cuts : List Nat}
(hc : cuts ≠ []) : ∃ best, bestRodCutOf price n cuts = some best := by
cases cuts with
| nil =>
exact False.elim (hc rfl)
| cons cut rest =>
simp [bestRodCutOf]
cases hrest : bestRodCutOf price n rest with
| none =>
exact ⟨cut, rfl⟩
| some restBest =>
exact ⟨betterRodCut price n cut restBest, rfl⟩
The optimal first cut for a rod of length n (junk value 0 when n = 0).
def rodCutFirstCut (price : Nat → Nat) (n : Nat) : Nat :=
(bestRodCutOf price n (rodCutCandidates n)).getD 0The chosen first cut is a valid first cut for a positive-length rod.
theorem rodCutFirstCut_mem (price : Nat → Nat) {n : Nat} (hn : 0 < n) :
rodCutFirstCut price n ∈ rodCutCandidates n := by
unfold rodCutFirstCut
have hne : rodCutCandidates n ≠ [] := by
cases n with
| zero => omega
| succ m => simp [rodCutCandidates]
rcases bestRodCutOf_exists_of_ne_nil price n hne with ⟨best, hbest⟩
rw [hbest]
simp
exact (bestRodCutOf_correct price n hbest).1The chosen first cut has revenue at least every candidate's.
theorem rodCutFirstCut_le (price : Nat → Nat) {n : Nat} (hn : 0 < n) (cut : Nat)
(hcut : cut ∈ rodCutCandidates n) :
FirstCutValue price (bottomUpRodRevenue price) n cut ≤
FirstCutValue price (bottomUpRodRevenue price) n (rodCutFirstCut price n) := by
unfold rodCutFirstCut
have hne : rodCutCandidates n ≠ [] := by
cases n with
| zero => omega
| succ m => simp [rodCutCandidates]
rcases bestRodCutOf_exists_of_ne_nil price n hne with ⟨best, hbest⟩
rw [hbest]
simp
exact (bestRodCutOf_correct price n hbest).2 cut hcut
The chosen first cut achieves the optimal revenue (CLRS §14.1: the s entry
attains the maximum in the recurrence).
theorem rodCutFirstCut_value (price : Nat → Nat) {n : Nat} (hn : 0 < n) :
FirstCutValue price (bottomUpRodRevenue price) n (rodCutFirstCut price n) =
bottomUpRodRevenue price n := by
have hs_mem : rodCutFirstCut price n ∈ Finset.Icc 1 n :=
(mem_rodCutCandidates n _).mp (rodCutFirstCut_mem price hn)
rcases n with _ | m
· omega
· rw [bottomUpRodRevenue_succ price m]
apply le_antisymm
· exact Finset.le_sup hs_mem
· refine Finset.sup_le ?_
intro cut hcut
exact rodCutFirstCut_le price hn cut ((mem_rodCutCandidates (m + 1) cut).mpr hcut)
Reconstruct an optimal cutting plan for a rod of length n by repeatedly taking
the optimal first cut (CLRS §14.1 PRINT-CUT-ROD-SOLUTION).
def rodCutPlan (price : Nat → Nat) : Nat → List Nat
| 0 => []
| n@(_ + 1) => rodCutFirstCut price n :: rodCutPlan price (n - rodCutFirstCut price n)
termination_by n => n
decreasing_by
simp_wf
have hmem : rodCutFirstCut price n ∈ rodCutCandidates n := rodCutFirstCut_mem price (by omega)
have hIcc := (mem_rodCutCandidates n (rodCutFirstCut price n)).mp hmem
have hpos : 1 ≤ rodCutFirstCut price n := (Finset.mem_Icc.mp hIcc).1
omega
The reconstructed plan for a rod of length n has total length n and
attains the optimal revenue.
theorem rodCutPlan_correct (price : Nat → Nat) (n : Nat) :
planLength (rodCutPlan price n) = n ∧
planValue price (rodCutPlan price n) = bottomUpRodRevenue price n := by
induction n using Nat.strong_induction_on with
| h n ih =>
cases n with
| zero =>
rw [rodCutPlan]
simp [planLength, planValue]
| succ m =>
set s : Nat := rodCutFirstCut price (m + 1) with hs_def
have hs_mem : s ∈ rodCutCandidates (m + 1) := by
rw [hs_def]
exact rodCutFirstCut_mem price (by omega)
have hs_Icc : s ∈ Finset.Icc 1 (m + 1) := (mem_rodCutCandidates (m + 1) s).mp hs_mem
have hs_pos : 1 ≤ s := (Finset.mem_Icc.mp hs_Icc).1
have hs_le : s ≤ m + 1 := (Finset.mem_Icc.mp hs_Icc).2
have hs_sub : m + 1 - s < m + 1 := by omega
have ih' := ih (m + 1 - s) hs_sub
have hs_value : FirstCutValue price (bottomUpRodRevenue price) (m + 1) s =
bottomUpRodRevenue price (m + 1) := by
rw [hs_def]
exact rodCutFirstCut_value price (by omega)
constructor
· rw [rodCutPlan]
simp only [planLength, List.sum_cons]
rw [← hs_def]
change s + planLength (rodCutPlan price (m + 1 - s)) = m + 1
rw [ih'.1]
omega
· rw [rodCutPlan]
simp only [planValue, List.map_cons, List.sum_cons]
rw [← hs_def]
change price s + planValue price (rodCutPlan price (m + 1 - s)) =
bottomUpRodRevenue price (m + 1)
rw [ih'.2]
simpa [FirstCutValue] using hs_valueThe reconstructed plan is optimal among all positive-piece plans of the same total length.
theorem rodCutPlan_optimal (price : Nat → Nat) (n : Nat) {other : List Nat}
(hother_pos : PositivePieces other) (hlen : planLength other = n) :
planValue price other ≤ planValue price (rodCutPlan price n) := by
have hc := rodCutPlan_correct price n
apply planValue_le_optimalPlanValue_of_same_length
(price := price) (revenue := bottomUpRodRevenue price)
(bottomUpRodRevenue_rodCutRecurrence price) hother_pos
· rw [hc.1]
exact hlen
· rw [hc.2, hc.1]Cost analysis: BOTTOM-UP-CUT-ROD runs in O(n^2)
The total number of first-cut candidates examined by BOTTOM-UP-CUT-ROD for a
rod of length n: rod length j scans all j possible first cuts, so the
total is ∑_{j = 1}^{n} j = n (n + 1) / 2.
def rodCutStepCount (n : Nat) : Nat :=
(Finset.range (n + 1)).sum (fun j => j)
Closed form: the bottom-up step count is n (n + 1) / 2.
theorem rodCutStepCount_eq (n : Nat) : rodCutStepCount n = n * (n + 1) / 2 := by
unfold rodCutStepCount
have h : (Finset.range (n + 1)).sum (fun j => j) * 2 = (n + 1) * n := by
simpa using (Finset.sum_range_id_mul_two (n + 1))
rw [Nat.mul_comm n (n + 1)]
rw [← h]
omega
The bottom-up rod-cutting algorithm performs O(n²) work: its step count is at
most n².
theorem rodCutStepCount_le_quadratic (n : Nat) : rodCutStepCount n ≤ n ^ 2 := by
rw [rodCutStepCount_eq]
by_cases hn : n = 0
· simp [hn]
· have hpos : 1 ≤ n := Nat.succ_le_of_lt (Nat.pos_of_ne_zero hn)
have hprod : n * (n + 1) ≤ 2 * n ^ 2 := by
nlinarith
have hdiv : n * (n + 1) / 2 ≤ (2 * n ^ 2) / 2 := by gcongr
have htwo : (2 * n ^ 2) / 2 = n ^ 2 := by omega
rwa [htwo] at hdiv
The candidate first cuts [1, ..., n] as a list of subtypes carrying the
bounds 1 ≤ c and c ≤ n, used to thread the memoized cache through the scan.
def rodCutCandidatesBounded (n : Nat) : List {c : Nat // 1 ≤ c ∧ c ≤ n} :=
(List.range n).attach.map (fun i => ⟨i.1 + 1, Nat.succ_pos i.1, by
have hi : i.1 < n := List.mem_range.mp i.2
omega⟩)A rod of length zero has no first-cut candidates.
@[simp] theorem rodCutCandidatesBounded_zero : rodCutCandidatesBounded 0 = [] := by
simp [rodCutCandidatesBounded]Projecting the bounded candidates recovers the plain candidate list.
theorem rodCutCandidatesBounded_map_val (n : Nat) :
(rodCutCandidatesBounded n).map (fun cut => cut.1) = rodCutCandidates n := by
simp [rodCutCandidatesBounded, rodCutCandidates]A memoized cache is consistent when every stored revenue is the true optimal revenue for its rod length.
def ConsistentCache (price : Nat → Nat) (cache : Nat → Option Nat) : Prop :=
∀ k v, cache k = some v → v = bottomUpRodRevenue price kA single memoized step, given the sub-computation result.
def memoizedRodCutStep (price : Nat → Nat) (cur : Nat) (subval : Nat)
(cacheSub : Nat → Option Nat) (cut : Nat) : Nat × (Nat → Option Nat) :=
(max cur (price cut + subval), cacheSub)
Top-down memoized rod cutting (CLRS MEMOIZED-CUT-ROD). memoizedRodCut price n
cache returns the optimal revenue for a rod of length n together with a cache
extended with the revenue of n and every subproblem it needed. Entries already
present in cache are reused without recomputation.
def memoizedRodCut (price : Nat → Nat) : Nat → (Nat → Option Nat) → Nat × (Nat → Option Nat)
| n, cache =>
match cache n with
| some v => (v, cache)
| none =>
let res := (rodCutCandidatesBounded n).foldl
(fun acc cut =>
let (cur, cacheAcc) := acc
let (subval, cacheSub) := memoizedRodCut price (n - cut.1) cacheAcc
memoizedRodCutStep price cur subval cacheSub cut.1)
(0, cache)
(res.1, Function.update res.2 n (some res.1))
termination_by n _ => n
decreasing_by
simp_wf
omega
The memoized cache-threading fold over the candidate cuts of rod length n.
Exposed so its correctness can be stated and proved by induction.
def memoizedRodCutFold (price : Nat → Nat) (n : Nat) (initVal : Nat) (cache : Nat → Option Nat)
(cuts : List {c : Nat // 1 ≤ c ∧ c ≤ n}) : Nat × (Nat → Option Nat) :=
cuts.foldl (fun acc cut =>
let (cur, cacheAcc) := acc
let (subval, cacheSub) := memoizedRodCut price (n - cut.1) cacheAcc
memoizedRodCutStep price cur subval cacheSub cut.1) (initVal, cache)List max helpers
Folding max is monotone in the accumulator.
theorem foldl_max_mono_init {l : List Nat} {a b : Nat} (h : a ≤ b) :
l.foldl max a ≤ l.foldl max b := by
induction l generalizing a b with
| nil => simpa using h
| cons c as ih =>
simp only [List.foldl]
exact ih (max_le_max_right c h)
Folding max with an upper-bound accumulator keeps the result below the
bound provided every element is below it.
theorem foldl_max_le_general {l : List Nat} {b init : Nat} (hb : init ≤ b)
(h : ∀ x ∈ l, x ≤ b) : l.foldl max init ≤ b := by
induction l generalizing init with
| nil => simpa using hb
| cons a as ih =>
simp only [List.foldl]
exact ih (max_le hb (h a (by simp))) (by
intro x hx
exact h x (by simp [hx]))
Folding max from zero is bounded by any upper bound of the elements.
theorem foldl_max_le {l : List Nat} {b : Nat} (h : ∀ x ∈ l, x ≤ b) :
l.foldl max 0 ≤ b :=
foldl_max_le_general (b := b) (init := 0) (by omega) h
Folding max from x stays above x.
theorem foldl_max_ge_init (l : List Nat) (x : Nat) : x ≤ l.foldl max x := by
induction l with
| nil => simp
| cons a as ih =>
simp only [List.foldl]
exact le_trans ih (foldl_max_mono_init (l := as) (le_max_left x a))
Any element of a list is below the fold-max value.
theorem foldl_max_mem {l : List Nat} {x : Nat} (hx : x ∈ l) : x ≤ l.foldl max 0 := by
induction l with
| nil => simp at hx
| cons a as ih =>
simp only [List.foldl] at hx ⊢
rcases List.mem_cons.mp hx with hxa | has
· subst x
have h0a : max 0 a = a := by simp
rw [h0a]
exact foldl_max_ge_init as a
· exact le_trans (ih has) (foldl_max_mono_init (l := as) (le_max_left 0 a))
The maximum over the first-cut candidates for rod length n is the optimal
revenue (this is exactly the CLRS first-cut recurrence).
theorem candidatesMax_eq_rev (price : Nat → Nat) (n : Nat) :
((rodCutCandidatesBounded n).map
(fun cut => FirstCutValue price (bottomUpRodRevenue price) n cut.1)).foldl max 0
= bottomUpRodRevenue price n := by
apply le_antisymm
· refine foldl_max_le ?_
intro v hv
rcases List.mem_map.mp hv with ⟨cut, hcut, rfl⟩
exact firstCutValue_le_of_rodCutRecurrence
(bottomUpRodRevenue_rodCutRecurrence price) (Finset.mem_Icc.mpr cut.2)
· by_cases hn : n = 0
· simp [hn, bottomUpRodRevenue_zero]
· have hpos : 0 < n := Nat.pos_of_ne_zero hn
have hs_value : FirstCutValue price (bottomUpRodRevenue price) n (rodCutFirstCut price n) =
bottomUpRodRevenue price n := rodCutFirstCut_value price hpos
rw [← hs_value]
apply foldl_max_mem
have hproj : (rodCutCandidatesBounded n).map (fun cut => cut.1) = rodCutCandidates n :=
rodCutCandidatesBounded_map_val n
have hrc : rodCutFirstCut price n ∈ (rodCutCandidatesBounded n).map (fun cut => cut.1) := by
rw [hproj]
exact rodCutFirstCut_mem price hpos
rcases List.mem_map.mp hrc with ⟨cut, hcut, hval⟩
apply List.mem_map.mpr
exact ⟨cut, hcut, by rw [← hval]⟩Memoization correctness
The memoized fold accumulates the best first-cut value and threads a consistent cache.
theorem memoizedRodCut_fold_correct (price : Nat → Nat) (n : Nat)
(cuts : List {c : Nat // 1 ≤ c ∧ c ≤ n})
(initVal : Nat) (cache : Nat → Option Nat) (hcons : ConsistentCache price cache)
(hrec : ∀ cut ∈ cuts, ∀ c', ConsistentCache price c' →
(memoizedRodCut price (n - cut.1) c').1 = bottomUpRodRevenue price (n - cut.1) ∧
ConsistentCache price (memoizedRodCut price (n - cut.1) c').2) :
(memoizedRodCutFold price n initVal cache cuts).1 =
(cuts.map (fun cut => price cut.1 + bottomUpRodRevenue price (n - cut.1))).foldl max initVal ∧
ConsistentCache price (memoizedRodCutFold price n initVal cache cuts).2 := by
unfold memoizedRodCutFold
induction cuts generalizing initVal cache with
| nil =>
constructor
· simp
· simpa using hcons
| cons head rest ih =>
have hval_head : (memoizedRodCut price (n - head.1) cache).1 = bottomUpRodRevenue price (n - head.1) :=
(hrec head (by simp) cache hcons).1
have hcons_head : ConsistentCache price (memoizedRodCut price (n - head.1) cache).2 :=
(hrec head (by simp) cache hcons).2
let s : Nat × (Nat → Option Nat) := memoizedRodCutStep price initVal
(memoizedRodCut price (n - head.1) cache).1 (memoizedRodCut price (n - head.1) cache).2 head.1
have hcons_s : ConsistentCache price s.2 := by simpa [s, memoizedRodCutStep] using hcons_head
have ih' := ih s.1 s.2 hcons_s (fun cut' hcut' c'' hc'' => hrec cut'
(List.mem_cons.mpr (Or.inr hcut')) c'' hc'')
simp only [List.foldl]
constructor
· rw [ih'.1]
have hs1 : s.1 = max initVal (price head.1 + bottomUpRodRevenue price (n - head.1)) := by
simp [s, memoizedRodCutStep, hval_head]
rw [hs1]
simp only [List.map, List.foldl]
· exact ih'.2
When the cache misses length n, the uncached computation reduces to the
cache-threading fold: its revenue component is the fold's first component.
theorem memoizedRodCut_none_fst {price : Nat → Nat} {n : Nat} {cache : Nat → Option Nat}
(hc : cache n = none) :
(memoizedRodCut price n cache).1 = (memoizedRodCutFold price n 0 cache (rodCutCandidatesBounded n)).1 := by
rw [memoizedRodCut, hc]
rfl
When the cache misses length n, the uncached computation reduces to the
cache-threading fold: its cache is the fold's cache with the entry at n
stored.
theorem memoizedRodCut_none_snd {price : Nat → Nat} {n : Nat} {cache : Nat → Option Nat}
(hc : cache n = none) :
(memoizedRodCut price n cache).2 =
Function.update (memoizedRodCutFold price n 0 cache (rodCutCandidatesBounded n)).2
n (some (memoizedRodCutFold price n 0 cache (rodCutCandidatesBounded n)).1) := by
rw [memoizedRodCut, hc]
rflMemoized top-down rod cutting is correct: it returns the optimal revenue and its output cache remains consistent.
theorem memoizedRodCut_correct (price : Nat → Nat) (n : Nat) (cache : Nat → Option Nat)
(hcons : ConsistentCache price cache) :
(memoizedRodCut price n cache).1 = bottomUpRodRevenue price n ∧
ConsistentCache price (memoizedRodCut price n cache).2 := by
revert hcons cache
induction n using Nat.strong_induction_on with
| h n ih =>
intro cache hcons
by_cases hc : cache n = none
· rcases n with _ | m
· -- n = 0, uncached
constructor
· rw [memoizedRodCut_none_fst hc]
simp [memoizedRodCutFold]
· rw [memoizedRodCut_none_snd hc]
intro k v hkv
by_cases hk : k = 0
· subst k
have hkv0 : 0 = v := by simpa [Function.update, memoizedRodCutFold] using hkv
rw [← hkv0]
simp
· simp [Function.update, hk] at hkv
exact hcons k v hkv
· -- n = m + 1, uncached
have hrec : ∀ cut ∈ rodCutCandidatesBounded (m + 1), ∀ c', ConsistentCache price c' →
(memoizedRodCut price (m + 1 - cut.1) c').1 = bottomUpRodRevenue price (m + 1 - cut.1) ∧
ConsistentCache price (memoizedRodCut price (m + 1 - cut.1) c').2 := by
intro cut hcut c' hc'
have hlt : m + 1 - cut.1 < m + 1 := by
have h1 : 1 ≤ cut.1 := cut.2.1
omega
exact ih (m + 1 - cut.1) hlt c' hc'
have hfold := memoizedRodCut_fold_correct price (m + 1) (rodCutCandidatesBounded (m + 1))
0 cache hcons hrec
constructor
· rw [memoizedRodCut_none_fst hc, hfold.1]
exact candidatesMax_eq_rev price (m + 1)
· rw [memoizedRodCut_none_snd hc]
intro k v hkv
by_cases hk : k = m + 1
· subst k
simp [Function.update] at hkv
rw [← hkv]
rw [hfold.1]
exact candidatesMax_eq_rev price (m + 1)
· simp [Function.update, hk] at hkv
exact hfold.2 k v hkv
· -- cached
cases hcv : cache n with
| none => exact False.elim (hc hcv)
| some v =>
have hv' : v = bottomUpRodRevenue price n := hcons n v hcv
constructor
· rw [memoizedRodCut, hcv]
simp
exact hv'
· rw [memoizedRodCut, hcv]
simp
exact hconsMemoized top-down rod cutting returns the optimal revenue.
theorem memoizedRodCut_value (price : Nat → Nat) (n : Nat) (cache : Nat → Option Nat)
(hcons : ConsistentCache price cache) :
(memoizedRodCut price n cache).1 = bottomUpRodRevenue price n :=
(memoizedRodCut_correct price n cache hcons).1The bottom-up execution realizes the previously defined triangular budget.
theorem rodExecution_candidates_eq (price : Nat → Nat) (n : Nat) :
(RodExecution.execute price n).candidates = rodCutStepCount n :=
RodExecution.execute_candidates price nQuadratic bound on actual candidate visits, independently of price values.
theorem rodExecution_candidates_le_quadratic (price : Nat → Nat) (n : Nat) :
(RodExecution.execute price n).candidates ≤ n ^ 2 := by
rw [rodExecution_candidates_eq]
exact rodCutStepCount_le_quadratic nend Chapter15end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_14.Section_14_1_Rod_Cutting.Execution
Counted bottom-up rod cutting
The outer loop computes its preceding array once. The inner loop reads that stored array once per candidate, retaining its running maximum. Counters record candidate evaluations and appended table entries in that same execution. Array access, price lookup, and natural-number operations are primitive events; their implementation internals and persistent-array copying are not counted.
namespace CLRS.Chapter15.RodExecutionstructure Scan where
value : Nat
candidates : Nat
deriving Repr
Scan cuts 1..k using an already computed revenue array.
def scan (price : Nat → Nat) (previous : Array Nat) (n : Nat) : Nat → Scan
| 0 => ⟨0, 0⟩
| k + 1 =>
let rest := scan price previous n k
let candidate := price (k + 1) + arrGet previous (n - (k + 1))
⟨max rest.value candidate, rest.candidates + 1⟩theorem scan_candidates (price : Nat → Nat) (previous : Array Nat) (n k : Nat) :
(scan price previous n k).candidates = k := by
induction k with
| zero => rfl
| succ k ih => simp [scan, ih]
theorem scan_value (price : Nat → Nat) (previous : Array Nat) (n k : Nat) :
(scan price previous n k).value =
(Finset.Icc 1 k).sup (fun i => price i + arrGet previous (n - i)) := by
induction k with
| zero => simp [scan]
| succ k ih =>
have hs : Finset.Icc 1 (k + 1) = insert (k + 1) (Finset.Icc 1 k) := by
ext i
simp only [Finset.mem_Icc, Finset.mem_insert]
omega
simp only [scan, ih, hs, Finset.sup_insert]
exact max_comm _ _structure Run where
array : Array Nat
candidates : Nat
writes : Nat
deriving ReprAppend each new optimum once, using only the previously stored prefix.
def execute (price : Nat → Nat) : Nat → Run
| 0 => ⟨#[0], 0, 1⟩
| j + 1 =>
let previous := execute price j
let row := scan price previous.array (j + 1) (j + 1)
⟨previous.array.push row.value,
previous.candidates + row.candidates, previous.writes + 1⟩The actual stored array refines the previously proved revenue table.
theorem execute_array (price : Nat → Nat) (n : Nat) :
(execute price n).array = rodRevenueArrayAux price n := by
induction n with
| zero => rfl
| succ n ih => simp only [execute, ih, scan_value, rodRevenueArrayAux]
theorem execute_entry (price : Nat → Nat) (n k : Nat) (hk : k ≤ n) :
arrGet (execute price n).array k = bottomUpRodRevenue price k := by
rw [execute_array]
exact arrGet_rodRevenueArrayAux price n k hkExact candidate visits obtained by adding counters of the executed scans.
theorem execute_candidates (price : Nat → Nat) (n : Nat) :
(execute price n).candidates = (Finset.range (n + 1)).sum (fun j => j) := by
induction n with
| zero => simp [execute]
| succ n ih => simp only [execute, ih, scan_candidates, Finset.sum_range_succ]Includes the boundary entry at length zero.
theorem execute_writes (price : Nat → Nat) (n : Nat) :
(execute price n).writes = n + 1 := by
induction n with
| zero => rfl
| succ n ih => simp [execute, ih]
theorem execute_size (price : Nat → Nat) (n : Nat) :
(execute price n).array.size = n + 1 := by
rw [execute_array, rodRevenueArrayAux_size]end CLRS.Chapter15.RodExecutionCLRSLean.Chapter_15.Section_15_1_Rod_Cutting
CLRS Section 15.1 - Rod cutting
This section formalizes the mathematical core of the rod-cutting dynamic program. Instead of committing immediately to one array implementation, it defines the Bellman first-cut recurrence as a specification for a revenue function. The main theorem proves that any revenue function satisfying that recurrence upper-bounds the value of every concrete cutting plan. Consequently, any plan whose value attains the recurrence value is optimal among plans of the same total length.
Main results:
-
Theorem
firstCutValue_le_of_rodCutRecurrence: every admissible first cut is bounded by the recurrence value. -
Theorem
rodRevenue_le_of_firstCutValue_bounds: the recurrence value is the least upper bound induced by first-cut candidates. -
Theorem
bottomUpRodRevenue_rodCutRecurrence: the executable recurrence-valued rod-cutting function satisfies the CLRS Bellman recurrence. -
Theorem
planValue_le_table_of_rodCutTableRecurrence: any finite table filled by the bottom-up recurrence is an upper bound for every positive-piece cutting plan within its filled prefix. -
Theorem
planValue_le_revenue_of_rodCutRecurrence: every positive-piece cutting plan is bounded by the recurrence value of its total length. -
Theorem
planValue_le_optimalPlanValue_of_same_length: a plan attaining the recurrence value is optimal among plans of the same length. -
Definition
rodRevenueArray: the CLRSBOTTOM-UP-CUT-RODtable built as a mutableArray Nat, whose entrykis filled from the earlier stored entries exactly as the imperative algorithm fillsr[0 .. n]. -
Theorem
rodRevenueArray_correct: the mutable-array bottom-up table refines the pure recurrence valuebottomUpRodRevenueat every filled index. -
Theorem
rodRevenueArray_rodCutTableRecurrence: reading the mutable-array table is a correct finite bottom-up table in the sense ofRodCutTableRecurrence. -
Theorem
planValue_le_rodRevenueArray: every positive-piece cutting plan is bounded by the value the mutable-array table stores at its total length.
Status: proved for the mathematical cut-optimality layer and the mutable-array
bottom-up implementation refinement.
Deferred refinements:
-
A top-down memoized-cache refinement and explicit RAM cost semantics remain future implementation-level targets. The sibling dynamic-programming sections (matrix chain, LCS, optimal BST) follow the same mutable-array bottom-up pattern established here.
namespace CLRSnamespace Chapter15Rod-cutting model
The total length of a concrete cutting plan.
def planLength (pieces : List Nat) : Nat :=
pieces.sumThe value of a cutting plan under the given price table.
def planValue (price : Nat → Nat) (pieces : List Nat) : Nat :=
(pieces.map price).sumEvery piece in the cutting plan has positive length.
def PositivePieces (pieces : List Nat) : Prop :=
∀ piece, piece ∈ pieces → 0 < piece
The value obtained by making cut the first cut of a rod of length n.
def FirstCutValue (price revenue : Nat → Nat) (n cut : Nat) : Nat :=
price cut + revenue (n - cut)The CLRS rod-cutting recurrence: length zero has value zero, and every positive length is the maximum over all possible first cuts.
def RodCutRecurrence (price revenue : Nat → Nat) : Prop :=
revenue 0 = 0 ∧
∀ n, revenue (n + 1) =
(Finset.Icc 1 (n + 1)).sup
(fun cut => FirstCutValue price revenue (n + 1) cut)Bottom-up table and executable recurrence
A finite bottom-up rod-cutting table is correct through limit when entry
zero is zero and every positive entry up to limit is filled by the CLRS
first-cut recurrence using earlier table entries.
def RodCutTableRecurrence (price table : Nat → Nat) (limit : Nat) : Prop :=
table 0 = 0 ∧
∀ n, n < limit →
table (n + 1) =
(Finset.Icc 1 (n + 1)).sup
(fun cut => FirstCutValue price table (n + 1) cut)
The canonical executable rod-cutting value function obtained by recursively
evaluating the CLRS first-cut recurrence. The recurrence is written over
Finset.attach so Lean sees that each recursive call is made at a strictly
smaller rod length.
def bottomUpRodRevenue (price : Nat → Nat) : Nat → Nat
| 0 => 0
| n + 1 =>
(Finset.Icc 1 (n + 1)).attach.sup
(fun cut => price cut.1 + bottomUpRodRevenue price ((n + 1) - cut.1))
termination_by n => n
decreasing_by
simp_wf
exact (Finset.mem_Icc.mp cut.2).1private theorem finset_attach_sup_eq (s : Finset Nat) (f : Nat → Nat) :
s.attach.sup (fun x => f x.1) = s.sup f := by
apply le_antisymm
· refine Finset.sup_le ?_
intro x _hx
exact Finset.le_sup (f := f) x.2
· refine Finset.sup_le ?_
intro x hx
exact Finset.le_sup (f := fun x : {x // x ∈ s} => f x.1)
(Finset.mem_attach s ⟨x, hx⟩)
@[simp] theorem bottomUpRodRevenue_zero (price : Nat → Nat) :
bottomUpRodRevenue price 0 = 0 := by
rw [bottomUpRodRevenue]The executable recurrence unfolds to the textbook first-cut maximum.
theorem bottomUpRodRevenue_succ (price : Nat → Nat) (n : Nat) :
bottomUpRodRevenue price (n + 1) =
(Finset.Icc 1 (n + 1)).sup
(fun cut => FirstCutValue price (bottomUpRodRevenue price) (n + 1) cut) := by
rw [bottomUpRodRevenue]
change (Finset.Icc 1 (n + 1)).attach.sup
(fun cut => price cut.1 + bottomUpRodRevenue price ((n + 1) - cut.1)) =
(Finset.Icc 1 (n + 1)).sup
(fun cut => price cut + bottomUpRodRevenue price ((n + 1) - cut))
simpa [FirstCutValue] using
(finset_attach_sup_eq (Finset.Icc 1 (n + 1))
(fun cut => price cut + bottomUpRodRevenue price ((n + 1) - cut)))The executable recurrence-valued function satisfies the CLRS recurrence.
theorem bottomUpRodRevenue_rodCutRecurrence (price : Nat → Nat) :
RodCutRecurrence price (bottomUpRodRevenue price) := by
constructor
· exact bottomUpRodRevenue_zero price
· intro n
exact bottomUpRodRevenue_succ price nA global recurrence function induces a correct finite table prefix.
theorem rodCutTableRecurrence_of_rodCutRecurrence {price revenue : Nat → Nat}
(hrec : RodCutRecurrence price revenue) (limit : Nat) :
RodCutTableRecurrence price revenue limit := by
constructor
· exact hrec.1
· intro n _hn
exact hrec.2 nEvery prefix of the executable recurrence-valued function is a correct table.
theorem bottomUpRodRevenue_rodCutTableRecurrence
(price : Nat → Nat) (limit : Nat) :
RodCutTableRecurrence price (bottomUpRodRevenue price) limit :=
rodCutTableRecurrence_of_rodCutRecurrence
(bottomUpRodRevenue_rodCutRecurrence price) limitFirst-cut recurrence facts
Every admissible first cut is bounded by the recurrence value.
theorem firstCutValue_le_of_rodCutRecurrence {price revenue : Nat → Nat}
(hrec : RodCutRecurrence price revenue) {n cut : Nat}
(hcut : cut ∈ Finset.Icc 1 n) :
FirstCutValue price revenue n cut ≤ revenue n := by
cases n with
| zero =>
simp at hcut
| succ n =>
rw [hrec.2 n]
exact Finset.le_sup hcutIf a number bounds every first-cut candidate, then it bounds the recurrence value. This is the upper-bound half of the Bellman maximum principle.
theorem rodRevenue_le_of_firstCutValue_bounds {price revenue : Nat → Nat}
(hrec : RodCutRecurrence price revenue) {n bound : Nat}
(hbound : ∀ cut, cut ∈ Finset.Icc 1 n →
FirstCutValue price revenue n cut ≤ bound) :
revenue n ≤ bound := by
cases n with
| zero =>
rw [hrec.1]
exact Nat.zero_le bound
| succ n =>
rw [hrec.2 n]
exact Finset.sup_le hboundSelling the whole rod as one piece is one admissible first-cut candidate.
theorem price_le_revenue_of_rodCutRecurrence {price revenue : Nat → Nat}
(hrec : RodCutRecurrence price revenue) {n : Nat} (hn : 1 ≤ n) :
price n ≤ revenue n := by
have hmem : n ∈ Finset.Icc 1 n := by
rw [Finset.mem_Icc]
exact ⟨hn, le_rfl⟩
have hcut := firstCutValue_le_of_rodCutRecurrence
(price := price) (revenue := revenue) hrec hmem
have hprice : price n ≤ FirstCutValue price revenue n n := by
unfold FirstCutValue
omega
exact Nat.le_trans hprice hcutBottom-up table facts
Every admissible first cut is bounded by the value stored in a correct finite bottom-up table, provided the queried rod length lies inside the filled prefix.
theorem firstCutValue_le_of_rodCutTableRecurrence {price table : Nat → Nat}
{limit n cut : Nat}
(htable : RodCutTableRecurrence price table limit)
(hn : n ≤ limit)
(hcut : cut ∈ Finset.Icc 1 n) :
FirstCutValue price table n cut ≤ table n := by
cases n with
| zero =>
simp at hcut
| succ n =>
rw [htable.2 n (Nat.lt_of_succ_le hn)]
exact Finset.le_sup hcutIf a number bounds every first-cut candidate inside a correct finite table prefix, then it bounds the stored table value.
theorem rodTableValue_le_of_firstCutValue_bounds {price table : Nat → Nat}
{limit n bound : Nat}
(htable : RodCutTableRecurrence price table limit)
(hn : n ≤ limit)
(hbound : ∀ cut, cut ∈ Finset.Icc 1 n →
FirstCutValue price table n cut ≤ bound) :
table n ≤ bound := by
cases n with
| zero =>
rw [htable.1]
exact Nat.zero_le bound
| succ n =>
rw [htable.2 n (Nat.lt_of_succ_le hn)]
exact Finset.sup_le hboundSelling the whole rod is also bounded by a correct finite table prefix.
theorem price_le_table_of_rodCutTableRecurrence {price table : Nat → Nat}
{limit n : Nat}
(htable : RodCutTableRecurrence price table limit)
(hn : 1 ≤ n) (hlimit : n ≤ limit) :
price n ≤ table n := by
have hmem : n ∈ Finset.Icc 1 n := by
rw [Finset.mem_Icc]
exact ⟨hn, le_rfl⟩
have hcut := firstCutValue_le_of_rodCutTableRecurrence
(price := price) (table := table) (limit := limit) htable hlimit hmem
have hprice : price n ≤ FirstCutValue price table n n := by
unfold FirstCutValue
omega
exact Nat.le_trans hprice hcutPlan optimality
Every concrete cutting plan with positive pieces is bounded by the recurrence value of its total length.
theorem planValue_le_revenue_of_rodCutRecurrence {price revenue : Nat → Nat}
(hrec : RodCutRecurrence price revenue) :
∀ pieces, PositivePieces pieces →
planValue price pieces ≤ revenue (planLength pieces)
| [], _hpos => by
simp [planValue, planLength, hrec.1]
| piece :: rest, hpos => by
have hpiece_pos : 0 < piece := by
exact hpos piece (by simp)
have hrest_pos : PositivePieces rest := by
intro x hx
exact hpos x (by simp [hx])
have ih :=
planValue_le_revenue_of_rodCutRecurrence
(price := price) (revenue := revenue) hrec rest hrest_pos
have hmem : piece ∈ Finset.Icc 1 (piece + planLength rest) := by
rw [Finset.mem_Icc]
exact ⟨Nat.succ_le_of_lt hpiece_pos, Nat.le_add_right piece (planLength rest)⟩
have hcut := firstCutValue_le_of_rodCutRecurrence
(price := price) (revenue := revenue) hrec hmem
have hcut' :
price piece + revenue (planLength rest) ≤
revenue (piece + planLength rest) := by
simpa [FirstCutValue, Nat.add_sub_cancel_left] using hcut
have hmono :
price piece + planValue price rest ≤
price piece + revenue (planLength rest) :=
Nat.add_le_add_left ih (price piece)
simpa [planValue, planLength] using Nat.le_trans hmono hcut'Every concrete cutting plan whose total length is inside a correct finite bottom-up table prefix is bounded by the table value at that total length.
theorem planValue_le_table_of_rodCutTableRecurrence {price table : Nat → Nat}
{limit : Nat}
(htable : RodCutTableRecurrence price table limit) :
∀ pieces, PositivePieces pieces → planLength pieces ≤ limit →
planValue price pieces ≤ table (planLength pieces)
| [], _hpos, _hlen => by
simp [planValue, planLength, htable.1]
| piece :: rest, hpos, hlen => by
have hpiece_pos : 0 < piece := by
exact hpos piece (by simp)
have hrest_pos : PositivePieces rest := by
intro x hx
exact hpos x (by simp [hx])
have htotal : piece + planLength rest ≤ limit := by
simpa [planLength] using hlen
have hrest_limit : planLength rest ≤ limit := by
omega
have ih : planValue price rest ≤ table (planLength rest) :=
planValue_le_table_of_rodCutTableRecurrence
(price := price) (table := table) (limit := limit)
htable rest hrest_pos hrest_limit
have hmem : piece ∈ Finset.Icc 1 (piece + planLength rest) := by
rw [Finset.mem_Icc]
exact ⟨Nat.succ_le_of_lt hpiece_pos,
Nat.le_add_right piece (planLength rest)⟩
have hfirst := firstCutValue_le_of_rodCutTableRecurrence
(price := price) (table := table) (limit := limit)
htable htotal hmem
have hfirstBound :
price piece + table (planLength rest) ≤
table (piece + planLength rest) := by
simpa [FirstCutValue, Nat.add_sub_cancel_left] using hfirst
have hmono :
price piece + planValue price rest ≤
price piece + table (planLength rest) :=
Nat.add_le_add_left ih (price piece)
simpa [planValue, planLength] using Nat.le_trans hmono hfirstBoundEvery positive-piece cutting plan is bounded by the executable recurrence value of its total length.
theorem planValue_le_bottomUpRodRevenue (price : Nat → Nat) :
∀ pieces, PositivePieces pieces →
planValue price pieces ≤ bottomUpRodRevenue price (planLength pieces) :=
planValue_le_revenue_of_rodCutRecurrence
(price := price) (revenue := bottomUpRodRevenue price)
(bottomUpRodRevenue_rodCutRecurrence price)If a cutting plan attains the recurrence value for its length, then every other positive-piece plan of the same total length has value at most that plan.
theorem planValue_le_optimalPlanValue_of_same_length
{price revenue : Nat → Nat} (hrec : RodCutRecurrence price revenue)
{candidate other : List Nat}
(hother_pos : PositivePieces other)
(hlen : planLength other = planLength candidate)
(hcandidate_value :
planValue price candidate = revenue (planLength candidate)) :
planValue price other ≤ planValue price candidate := by
have hother_bound :=
planValue_le_revenue_of_rodCutRecurrence
(price := price) (revenue := revenue) hrec other hother_pos
rw [hlen, ← hcandidate_value] at hother_bound
exact hother_boundIf a cutting plan attains the table value inside a correct finite bottom-up prefix, then every other positive-piece plan of the same length has value at most that plan.
theorem planValue_le_tablePlanValue_of_same_length
{price table : Nat → Nat} {limit : Nat}
(htable : RodCutTableRecurrence price table limit)
{candidate other : List Nat}
(hother_pos : PositivePieces other)
(hlen : planLength other = planLength candidate)
(hcandidate_value :
planValue price candidate = table (planLength candidate))
(hcandidate_len : planLength candidate ≤ limit) :
planValue price other ≤ planValue price candidate := by
have hother_len : planLength other ≤ limit := by
rw [hlen]
exact hcandidate_len
have hother_bound :=
planValue_le_table_of_rodCutTableRecurrence
(price := price) (table := table) (limit := limit)
htable other hother_pos hother_len
rw [hlen, ← hcandidate_value] at hother_bound
exact hother_boundIf a cutting plan attains the executable recurrence value for its length, then every other positive-piece plan of the same length has value at most that plan.
theorem planValue_le_bottomUpRodPlanValue_of_same_length
{price : Nat → Nat} {candidate other : List Nat}
(hother_pos : PositivePieces other)
(hlen : planLength other = planLength candidate)
(hcandidate_value :
planValue price candidate =
bottomUpRodRevenue price (planLength candidate)) :
planValue price other ≤ planValue price candidate :=
planValue_le_optimalPlanValue_of_same_length
(price := price) (revenue := bottomUpRodRevenue price)
(bottomUpRodRevenue_rodCutRecurrence price)
hother_pos hlen hcandidate_valueMutable-array bottom-up implementation
This block refines the pure recurrence-valued rod-cutting function to CLRS's
BOTTOM-UP-CUT-ROD, which fills a physical revenue table r[0 .. n] from
the bottom up. We model the table as an actual Array Nat grown one slot at
a time with Array.push, reusing the mutable-Array pattern of Section
17.4, and prove a refinement theorem: reading the mutable table at any filled index
returns exactly the pure recurrence value bottomUpRodRevenue. As a
corollary the array read satisfies RodCutTableRecurrence, so the mutable
table inherits the plan-optimality guarantees proved above.
Total read from a Nat-valued array, returning 0 for out-of-range
indices. This models the invariant-guarded reads a bottom-up dynamic-programming
table performs: every access made by the algorithm is in range, and the default
never fires during a real fill.
def arrGet (a : Array Nat) (i : Nat) : Nat := (a[i]?).getD 0Reading an in-range index is unaffected by pushing a new trailing element.
theorem arrGet_push_lt (a : Array Nat) (x i : Nat) (h : i < a.size) :
arrGet (a.push x) i = arrGet a i := by
unfold arrGet
rw [Array.getElem?_push, if_neg (Nat.ne_of_lt h)]Reading the freshly pushed slot returns the pushed element.
theorem arrGet_push_size (a : Array Nat) (x : Nat) :
arrGet (a.push x) a.size = x := by
unfold arrGet
rw [Array.getElem?_push, if_pos rfl]
rfl
Bottom-up mutable-array construction of the rod-cutting revenue table. The value
rodRevenueArrayAux price n is an Array Nat of length n + 1
whose entry k is the best revenue for a rod of length k. Each new
entry is computed as the CLRS first-cut maximum over Finset.Icc 1 (j + 1),
reading the earlier revenues that are already stored in the array - the imperative
BOTTOM-UP-CUT-ROD inner loop q = max(q, p[i] + r[j - i]).
def rodRevenueArrayAux (price : Nat → Nat) : Nat → Array Nat
| 0 => #[0]
| j + 1 =>
let previous := rodRevenueArrayAux price j
previous.push
((Finset.Icc 1 (j + 1)).sup
(fun i => price i + arrGet previous ((j + 1) - i)))
The bottom-up table for rod length n stores exactly n + 1 entries.
theorem rodRevenueArrayAux_size (price : Nat → Nat) (n : Nat) :
(rodRevenueArrayAux price n).size = n + 1 := by
induction n with
| zero => rfl
| succ j ih => simp only [rodRevenueArrayAux, Array.size_push, ih]
Refinement of the mutable-array fill to the recurrence value. Every filled
entry of the bottom-up array equals the pure recurrence value
bottomUpRodRevenue at the same index. The proof is an induction on the
table length whose step reads back the earlier entries the fill relied upon.
theorem arrGet_rodRevenueArrayAux (price : Nat → Nat) :
∀ n k, k ≤ n → arrGet (rodRevenueArrayAux price n) k = bottomUpRodRevenue price k := by
intro n
induction n with
| zero =>
intro k hk
obtain rfl : k = 0 := Nat.le_zero.mp hk
simp only [rodRevenueArrayAux, bottomUpRodRevenue_zero]
rfl
| succ j ih =>
intro k hk
have hsize : (rodRevenueArrayAux price j).size = j + 1 :=
rodRevenueArrayAux_size price j
simp only [rodRevenueArrayAux]
rcases Nat.eq_or_lt_of_le hk with heq | hlt
· -- k is the newly pushed top entry j + 1
have hk_size : k = (rodRevenueArrayAux price j).size := by rw [hsize]; exact heq
rw [hk_size, arrGet_push_size, ← hk_size, heq]
have hsup :
(Finset.Icc 1 (j + 1)).sup
(fun i => price i + arrGet (rodRevenueArrayAux price j) ((j + 1) - i))
= (Finset.Icc 1 (j + 1)).sup
(fun i => price i + bottomUpRodRevenue price ((j + 1) - i)) := by
apply Finset.sup_congr rfl
intro i hi
have hle : (j + 1) - i ≤ j := by
have := (Finset.mem_Icc.mp hi).1
omega
rw [ih ((j + 1) - i) hle]
rw [hsup, bottomUpRodRevenue_succ]
simp only [FirstCutValue]
· -- k lies inside the already-filled prefix
have hkj : k ≤ j := Nat.lt_succ_iff.mp hlt
have hk_lt : k < (rodRevenueArrayAux price j).size := by rw [hsize]; omega
rw [arrGet_push_lt _ _ _ hk_lt]
exact ih k hkj
The CLRS BOTTOM-UP-CUT-ROD revenue table for a rod of length n,
materialized as a mutable Array Nat of length n + 1.
def rodRevenueArray (price : Nat → Nat) (n : Nat) : Array Nat :=
rodRevenueArrayAux price n
The materialized table has one entry per rod length in 0 .. n.
theorem rodRevenueArray_size (price : Nat → Nat) (n : Nat) :
(rodRevenueArray price n).size = n + 1 :=
rodRevenueArrayAux_size price n
Correctness of the mutable-array bottom-up rod cutting. For every rod length
k inside the filled prefix, the entry the imperative table stores equals the
pure recurrence value bottomUpRodRevenue. This is the refinement theorem
requested by the mutable-array dynamic-programming layer of CLRS Chapter 15.
theorem rodRevenueArray_correct (price : Nat → Nat) (n k : Nat) (hk : k ≤ n) :
arrGet (rodRevenueArray price n) k = bottomUpRodRevenue price k :=
arrGet_rodRevenueArrayAux price n k hk
The top table entry is the best revenue for the full rod of length n.
theorem rodRevenueArray_full (price : Nat → Nat) (n : Nat) :
arrGet (rodRevenueArray price n) n = bottomUpRodRevenue price n :=
rodRevenueArray_correct price n n (le_refl n)
Reading the mutable-array table is a correct finite bottom-up table: its entry
zero is zero and every positive entry inside the filled prefix is the CLRS
first-cut maximum over earlier table entries. This connects the imperative
computation to the abstract RodCutTableRecurrence interface.
theorem rodRevenueArray_rodCutTableRecurrence (price : Nat → Nat) (n : Nat) :
RodCutTableRecurrence price (fun k => arrGet (rodRevenueArray price n) k) n := by
constructor
· show arrGet (rodRevenueArray price n) 0 = 0
rw [rodRevenueArray_correct price n 0 (Nat.zero_le n), bottomUpRodRevenue_zero]
· intro m hm
show arrGet (rodRevenueArray price n) (m + 1)
= (Finset.Icc 1 (m + 1)).sup
(fun cut => FirstCutValue price (fun k => arrGet (rodRevenueArray price n) k) (m + 1) cut)
rw [rodRevenueArray_correct price n (m + 1) hm, bottomUpRodRevenue_succ]
apply Finset.sup_congr rfl
intro cut hcut
unfold FirstCutValue
have hle : (m + 1) - cut ≤ n := by
have := (Finset.mem_Icc.mp hcut).1
omega
show price cut + bottomUpRodRevenue price ((m + 1) - cut)
= price cut + arrGet (rodRevenueArray price n) ((m + 1) - cut)
rw [rodRevenueArray_correct price n ((m + 1) - cut) hle]Plan optimality through the mutable-array table. Every positive-piece cutting plan whose total length lies inside the filled prefix is bounded by the value the mutable-array bottom-up table stores at that length. This lifts the refinement to the same end-to-end optimality statement proved for the pure recurrence table.
theorem planValue_le_rodRevenueArray (price : Nat → Nat) (n : Nat) :
∀ pieces, PositivePieces pieces → planLength pieces ≤ n →
planValue price pieces ≤ arrGet (rodRevenueArray price n) (planLength pieces) :=
planValue_le_table_of_rodCutTableRecurrence
(rodRevenueArray_rodCutTableRecurrence price n)end Chapter15end CLRS