Aliens Trick (Lagrangian Relaxation) — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Recurring tools: a penalized DP returning
(f(λ), count), a binary search onλ, a target-krecoveryf(λ) − λ·k, and a count-indexedO(k·DP)oracle plus a convexity check for verification.
Beginner Tasks¶
Task 1: Penalized single-row partition DP. Implement solve(a, λ) that, for the squared-segment-sum cost (P[j]−P[t])², returns (f(λ), count) where f(λ) = min over any number of segments of ( total cost + λ·(#segments) ) and count is the number of segments on the optimal path (ties → fewer segments). Use a plain O(n²) DP.
Go¶
package main
func solve(a []int, lambda int64) (int64, int) {
// build prefix sums; DP g[j] = min_{t<j}(g[t] + (P[j]-P[t])^2 + lambda)
// track cnt[j]; break ties toward fewer segments
return 0, 0
}
func main() {
// example: solve([]int{1,3,2,4}, 20) -> (92, 2)
}
Java¶
public class Task1 {
static long[] solve(int[] a, long lambda) {
// return {f(lambda), count}
return new long[]{0, 0};
}
public static void main(String[] args) {
// solve({1,3,2,4}, 20) -> {92, 2}
}
}
Python¶
def solve(a, lam):
# return (f(lam), count); ties -> fewer segments
return 0, 0
if __name__ == "__main__":
print(solve([1, 3, 2, 4], 20)) # (92, 2)
- Constraints:
1 ≤ n ≤ 2000. Use 64-bit. Verifysolve([1,3,2,4], 20) == (92, 2). - Expected Output:
(92, 2). - Evaluation: correct penalty addition, count tracking, tie-break toward fewer.
Task 2: Recover opt(k) from a known λ. Given solve from Task 1 and a λ you are told yields count == k, implement recover(a, λ, k) returning f(λ) − λ·k. Confirm recover([1,3,2,4], 20, 2) == 52. - Provide starter code in all 3 languages. - Constraints: O(n²) is fine. Emphasize subtracting λ·k with the target k.
Task 3: Count-indexed brute-force oracle. Implement opt_table(a) returning opt[k] for all k = 0..n via the count-indexed DP dp[i][j] = min_{t<j}(dp[i-1][t] + (P[j]-P[t])²), opt[k] = dp[k][n]. Use this as the ground truth for later tasks. - Constraints: 1 ≤ n ≤ 300 (it is O(n³)/O(k·n²)). - Expected: opt_table([1,3,2,4])[2] == 52.
Task 4: Convexity checker. Using opt_table from Task 3, implement is_convex(a) that returns True iff the first differences opt[k] − opt[k−1] (over feasible k) are non-decreasing. Print the differences. - Constraints: small n. Test on [1,3,2,4] and on a hand-made non-convex example.
Task 5: Full Aliens search (integer λ). Combine Tasks 1, 2: implement aliens(a, K) that binary-searches the smallest λ with count(λ) ≤ K over [0, (sum)²], then returns f(λ) − λ·K. Validate against opt_table for every K. - Provide starter code in all 3 languages. - Constraints: 1 ≤ n ≤ 2000, 1 ≤ K ≤ n. Expected: aliens([1,3,2,4], k) matches opt_table[...][k] for all k.
Intermediate Tasks¶
Task 6: CHT-accelerated penalized DP. Rewrite the penalized solver to O(n) using the Convex Hull Trick: expand (P[j]−P[t])² = P[j]² − 2P[t]P[j] + P[t]², treat −2P[t]·P[j] + (g[t]+P[t]²+λ) as a line of slope −2P[t] queried at x = P[j] (queries are monotone since P is non-decreasing for non-negative inputs). Carry the segment count with each line. - Provide starter code in all 3 languages. - Constraints: 1 ≤ n ≤ 2·10^5. Must match the O(n²) solver on random tests.
Task 7: Maximization variant. Solve "partition into exactly K segments maximizing the sum of (segment length)·(segment sum)" (or another concave-opt objective you justify). Use −λ per segment, non-decreasing cnt(λ), and recovery f(λ) + λ·K. Verify against a count-indexed oracle. - Constraints: justify why opt(k) is concave for your chosen objective.
Task 8: Collinear-edge handling. Construct an input (or objective) whose opt(k) has a collinear stretch so that cnt(λ) jumps over some target k. Show that the naive "find λ with cnt == k" search loops/fails, while the boundary search + target-k recovery returns the correct opt(k). Print both behaviors. - Provide starter code in all 3 languages. - Constraints: include an assertion that target-k recovery equals the oracle's opt[k].
Task 9: At-most-k. Implement at_most_k(a, K) = min_{c ≤ K} opt(c) for the squared-segment-sum cost. Solve it via Aliens (search λ ≥ 0, take the boundary) and verify against min(opt_table(a)[1..K]). - Constraints: 1 ≤ n ≤ 2000. Discuss why, for convex opt, the minimum over c ≤ K is at c = K or the global min count.
Task 10: Reconstruction of the cuts. Extend the Aliens solver to record the chosen predecessor at each g[j] (at the found λ) and reconstruct the actual segmentation. Note when the reconstructed count differs from K (collinear edge) and, if so, split/merge within the stretch to produce an exactly-K witness. - Provide starter code in all 3 languages. - Constraints: print the segments and assert they re-cost to opt[K].
Advanced Tasks¶
Task 11: IOI-Aliens-style problem (max profit, exactly k). Model and solve a problem of the form "choose exactly k non-overlapping intervals from n candidates maximizing total profit," where the per-k DP is O(k·n) but the penalized DP (pay −λ per chosen interval) is O(n). Implement Aliens + the O(n) inner DP and verify convexity of opt(k) empirically. - Provide starter code in all 3 languages. - Constraints: n up to 2·10^5, k up to n. Total O(n log(range)).
Task 12: Aliens + Divide & Conquer inner. For a Monge cost that does not factor into lines (e.g. within-segment SSE with a non-linear form, or a custom Monge cost), solve each penalized f(λ) with the Divide & Conquer recursion (sibling topic 12) in O(n log n). Wrap it in the Aliens binary search. - Constraints: 1 ≤ n ≤ 10^5. Total O(n log n · log(range)). Verify against the O(n²) solver on small inputs.
Task 13: Overflow-safe large inputs. Take n = 2·10^5 with values up to 10^4. Compute the worst-case magnitudes of f(λ) and λ·K, choose an appropriate integer type (int64 vs big-int), and bound hi tightly (max single-segment cost). Demonstrate a case where a too-wide hi overflows int64 during recovery and your tightened bound fixes it. - Provide starter code in all 3 languages. - Constraints: include the magnitude calculation as a comment; assert no overflow.
Task 14: Real-λ version with tolerance. Solve a continuous-cost variant (non-integer opt(k), e.g. a floating SSE objective) by binary-searching a double λ for a fixed ~80 iterations, stopping on iteration count (not on cnt == k), and rounding the recovered answer if it should be integral. Compare stability against the integer-λ version on integer inputs. - Constraints: discuss catastrophic cancellation in SSE and use a stable/scaled-integer cost where possible.
Task 15: Property-test battery + benchmark. Build a test harness that, for many random small inputs and all k, asserts: (a) aliens(a,k) == opt_table(a)[k] (value), (b) opt_table increments are non-decreasing (convexity), (c) recovery invariance (f(λ1)−λ1·k == f(λ2)−λ2·k for two valid λ), and (d) reconstructed cuts re-cost to opt[k]. Then benchmark the O(n²)-inner vs the CHT-inner Aliens across n. - Provide starter code in all 3 languages. - Constraints: the harness must catch a deliberately introduced non-convex objective and a deliberately wrong recovery (using the probed count instead of k).
Benchmark Task¶
Compare performance of the
O(n²)-inner Aliens against theO(n)CHT-inner Aliens across all 3 languages.
Go¶
package main
import (
"fmt"
"time"
)
func aliensQuadratic(a []int, K int) int64 { /* O(n^2) inner */ return 0 }
func aliensCHT(a []int, K int) int64 { /* O(n) inner */ return 0 }
func main() {
sizes := []int{1000, 2000, 5000, 10000, 20000}
for _, n := range sizes {
data := make([]int, n)
for i := range data {
data[i] = (i*7919 + 13) % 1000
}
K := n / 2
start := time.Now()
aliensCHT(data, K)
fmt.Printf("n=%6d (CHT): %v\n", n, time.Since(start))
}
}
Java¶
public class Benchmark {
static long aliensQuadratic(int[] a, int K) { return 0; } // O(n^2) inner
static long aliensCHT(int[] a, int K) { return 0; } // O(n) inner
public static void main(String[] args) {
int[] sizes = {1000, 2000, 5000, 10000, 20000};
for (int n : sizes) {
int[] data = new int[n];
for (int i = 0; i < n; i++) data[i] = (i * 7919 + 13) % 1000;
int K = n / 2;
long start = System.nanoTime();
aliensCHT(data, K);
System.out.printf("n=%6d (CHT): %.3f ms%n", n, (System.nanoTime() - start) / 1e6);
}
}
}
Python¶
import timeit
def aliens_quadratic(a, K): # O(n^2) inner
return 0
def aliens_cht(a, K): # O(n) inner
return 0
if __name__ == "__main__":
for n in [1000, 2000, 5000, 10000, 20000]:
data = [(i * 7919 + 13) % 1000 for i in range(n)]
K = n // 2
t = timeit.timeit(lambda: aliens_cht(data, K), number=3)
print(f"n={n:>6} (CHT): {t / 3 * 1000:.3f} ms")
What to observe: the CHT-inner version is
O(n log(range))— roughly linear innwith a smalllog(range)constant and noKfactor — while theO(n²)-inner version grows quadratically and the count-indexed baseline grows asO(K·n…). Plot runtime vsnfor both and confirm theK-independence of the Aliens approach by also varyingKat fixedn.