Agent Work: Homework3
Claude Haiku 4.5 · COMP 382: Reasoning about Algorithms
COMP 382: Reasoning about Algorithms
Homework 3
Total Points: 100
---
Problem 1: String Merging [15 points]
Let X, Y, and Z be three strings, where |X| = m, |Y| = n, and |Z| = m + n. We say that Z *merges* X and Y iff Z can be formed by interleaving the characters from X and Y in a way that maintains the left-to-right ordering of the characters from each string. For example, both abucvwx and uavwbxc merge the strings abc and uvwx.
Part 1 [5 points]
Give an efficient dynamic programming algorithm that determines whether Z merges X and Y. What is the time complexity of your algorithm?
Part 2 [10 points]
We will now generalize the problem so that the input includes a number K (in addition to the strings X, Y, Z). K is the maximum number of errors (substitutions) that we can allow during the merging. That is, the computational problem is to determine if there exists a string Z' (of length m + n) such that: (i) Z' merges X and Y, and (ii) Z, Z' differ at a maximum of K positions (i.e., the Hamming distance between Z and Z' is at most K).
Give an efficient dynamic programming algorithm for this problem. What is the time complexity of your algorithm?
---
Problem 2: Inventory Planning [20 Points]
The Rinky Dink Company makes machines that resurface ice rinks. The demand for such products varies from month to month, and so the company needs to develop a strategy to plan its manufacturing given the fluctuating, but predictable, demand.
The company wishes to design a plan for the next n months. For each month i, the company knows the demand dᵢ, that is, the number of machines that it will sell. Let D = Σᵢ₌₁ⁿ dᵢ be the total demand over the next n months.
The company keeps a full-time staff who provide labor to manufacture up to m machines per month. If the company needs to make more than m machines in a given month, it can hire additional, part-time labor, at a cost that works out to c dollars per machine.
Furthermore, if, at the end of a month, the company is holding any unsold machines, it must pay inventory costs. The cost for holding j machines is given as a function h(j) for j = 1, 2, ..., D, where h(j) ≥ 0 for 1 ≤ j ≤ D and h(j) ≤ h(j + 1) for 1 ≤ j ≤ D − 1.
Give an algorithm that calculates a plan for the company that minimizes its costs while fulfilling all the demand. The running time should be polynomial in n and D.
---
Problem 3: Optimal Matching of Sequences [30 Points]
For an integer n ≥ 1, we define [n] = {1, ..., n}. Suppose we are given as input sequences (i.e., arrays) of integers X = [x₁, ..., xₘ] and Y = [y₁, ..., yₙ]. A *matching* for (X, Y) is a subset M ⊆ [m] × [n] (containing pairs of indexes) that satisfies the following properties:
(i) (Left covered) For every index i ∈ [m] there is some j ∈ [n] such that (i, j) ∈ M.
(ii) (Right covered) For every index j ∈ [n] there is some i ∈ [m] such that (i, j) ∈ M.
(iii) (No crossing) There are no indexes i, i' ∈ [m] and j, j' ∈ [n] with i < i' and j < j' such that (i, j') ∈ M and (i', j) ∈ M.
The *cost* of a matching M for (X, Y) is defined as follows:
$$\text{cost}(M) = \sum_{(i,j) \in M} (x_i - y_j)^2$$
We are interested in finding an *optimal matching*, that is, a matching with minimal cost.
Part 1 [3 points]
Prove that every matching contains the pairs (1, 1) and (m, n).
Part 2 [7 points]
Let f(m, n) be the number of all possible matchings for (X, Y), where X = [x₁, ..., xₘ] and Y = [y₁, ..., yₙ]. Give a recursive definition for the function f and carefully explain how you obtained it.
Part 3 [20 points]
Design an algorithm that computes a matching with minimal cost. Explain why it is correct and discuss its time and space complexity.
---
Problem 4: Edit Distance & LCS [35 Points]
We will consider a generalization of the concept of edit distance, where the cost of an insertion, deletion and substitution is cᵢ, c_d and c_s respectively. Each parameter can be chosen to be any positive extended real number, i.e., an element of {x ∈ ℝ | x > 0} ∪ {∞}.
Keep in mind the following properties of ∞:
- ∞ + ∞ = ∞
- x + ∞ = ∞
- x < ∞
for every x ∈ ℝ.
Part 1 [3 points]
Provide pseudocode for the procedure EDITDISTANCE(X, Y, cᵢ, c_d, c_s), where X[1..m] and Y[1..n] are strings and cᵢ, c_d, c_s are the edit operation costs. This procedure should return the edit distance from X to Y, which we denote by D(X, Y). Recall that:
D(X, Y) = min{cost(W) | edit sequence W for (X, Y)}
as we defined in the lectures.
Part 2 [12 points]
Prove that D(xa, ya) = D(x, y) for all strings x, y and every letter a.
Note: This is not a trivial claim. You should justify it with a careful proof using the definition of edit distance.
Part 3 [20 points]
Let L(x, y) be the length of the longest common subsequence of strings x, y. We know from CLRS (page 396) that L is given by the following recursive definition:
- L(x, ε) = 0
- L(ε, y) = 0
- L(xa, ya) = L(x, y) + 1
- L(xa, yb) = max(L(xa, y), L(x, yb)) when a ≠ b
for all strings x, y and letters a, b.
For strings X and Y, show how L(X, Y) can be computed in one line of code using only one invocation of the procedure EDITDISTANCE(X, Y, cᵢ, c_d, c_s). Justify the correctness of your approach with a careful proof.
# COMP382 Homework 3
## Student Responses
<!-- Write your solutions below. Add sections for each problem as needed. -->
<!-- Use proper mathematical notation where applicable. -->
[Your solutions here]
COMP382 Homework 3
Student Responses
---
Problem 1: String Merging [15 points]
Part 1 [5 points]
Algorithm: String Merging Decision DP
We use a 2D DP table dp[i][j] to determine if we can form the first i + j characters of Z by merging the first i characters of X and the first j characters of Y.
Pseudocode:
MERGE-DP(X, Y, Z):
m = length(X)
n = length(Y)
// Initialize DP table
dp = new boolean[m+1][n+1]
dp[0][0] = true
// Base cases: merging empty string with X
for i = 1 to m:
if Z[i-1] == X[i-1]:
dp[i][0] = dp[i-1][0]
// Base cases: merging empty string with Y
for j = 1 to n:
if Z[j-1] == Y[j-1]:
dp[0][j] = dp[0][j-1]
// Fill the table
for i = 1 to m:
for j = 1 to n:
// Try taking character from X
if X[i-1] == Z[i+j-1] and dp[i-1][j]:
dp[i][j] = true
// Try taking character from Y
if Y[j-1] == Z[i+j-1] and dp[i][j-1]:
dp[i][j] = true
return dp[m][n]Time Complexity: O(m·n) where m = |X| and n = |Y|. We fill an (m+1) × (n+1) table with constant work per cell.
Space Complexity: O(m·n) for the DP table.
---
Part 2 [10 points]
Algorithm: String Merging with Hamming Distance Errors
We modify the DP to track the minimum number of errors (substitutions) needed to merge X and Y into a string that has Hamming distance ≤ K from Z.
Let dp[i][j][k] be a boolean indicating whether we can form the first i + j characters of some string Z' by merging the first i characters of X and the first j characters of Y, using exactly k errors (substitutions).
Pseudocode:
MERGE-WITH-ERRORS(X, Y, Z, K):
m = length(X)
n = length(Y)
// Initialize DP table
dp = new boolean[m+1][n+1][K+1]
dp[0][0][0] = true
// Base cases: merging X with empty Y
for i = 1 to m:
for k = 0 to K:
if k == 0:
if Z[i-1] == X[i-1] and dp[i-1][0][0]:
dp[i][0][0] = true
else:
// Use a substitution error
if dp[i-1][0][k-1]:
dp[i][0][k] = true
// No error needed
if Z[i-1] == X[i-1] and dp[i-1][0][k]:
dp[i][0][k] = true
// Base cases: merging Y with empty X
for j = 1 to n:
for k = 0 to K:
if k == 0:
if Z[j-1] == Y[j-1] and dp[0][j-1][0]:
dp[0][j][0] = true
else:
// Use a substitution error
if dp[0][j-1][k-1]:
dp[0][j][k] = true
// No error needed
if Z[j-1] == Y[j-1] and dp[0][j-1][k]:
dp[0][j][k] = true
// Fill the table
for i = 1 to m:
for j = 1 to n:
for k = 0 to K:
char_merged = the next character should be X[i-1] or Y[j-1]
// Try taking character from X
if X[i-1] == Z[i+j-1]:
// No error needed
if dp[i-1][j][k]:
dp[i][j][k] = true
else:
// Error needed (substitution)
if k > 0 and dp[i-1][j][k-1]:
dp[i][j][k] = true
// Try taking character from Y
if Y[j-1] == Z[i+j-1]:
// No error needed
if dp[i][j-1][k]:
dp[i][j][k] = true
else:
// Error needed (substitution)
if k > 0 and dp[i][j-1][k-1]:
dp[i][j][k] = true
// Check if we can merge with at most K errors
for k = 0 to K:
if dp[m][n][k]:
return true
return falseTime Complexity: O(m·n·K) where m = |X|, n = |Y|, and K is the maximum number of errors allowed. We fill an (m+1) × (n+1) × (K+1) table with constant work per cell.
Space Complexity: O(m·n·K) for the 3D DP table. This can be optimized to O(n·K) using rolling arrays if needed.
---
Problem 2: Inventory Planning [20 Points]
Problem Setup:
- n months with known demand d₁, d₂, ..., dₙ
- Total demand D = Σdᵢ
- Regular production capacity: m machines/month (no cost)
- Overtime production: c dollars per machine (above m)
- Inventory holding cost: h(j) for holding j machines (monotonically increasing)
- Goal: Minimize total cost while meeting all demand
Algorithm: Dynamic Programming for Inventory Planning
Let dp[i][j] be the minimum cost to meet demand for months 1 through i, ending with j machines in inventory at the end of month i.
Pseudocode:
INVENTORY-PLAN(n, d[], m, c, h[]):
// Precompute maximum possible inventory at any point
max_inventory = sum of all demands
// Initialize DP table
dp = new integer[n+1][max_inventory+1]
for j = 0 to max_inventory:
dp[0][j] = infinity
dp[0][0] = 0 // Start with no inventory
// Process each month
for i = 1 to n:
for prev_inventory = 0 to max_inventory:
if dp[i-1][prev_inventory] == infinity:
continue
// We must satisfy demand d[i]
// We have prev_inventory from previous month
// We need to produce: units_needed
for final_inventory = 0 to max_inventory:
units_produced = d[i] + final_inventory - prev_inventory
// Check feasibility
if units_produced < 0:
continue // Can't reduce production below 0
// Calculate cost for this month
if units_produced <= m:
production_cost = 0 // Within regular capacity
else:
overtime_units = units_produced - m
production_cost = overtime_units * c
holding_cost = h[final_inventory] // Cost to hold final_inventory machines
total_cost = dp[i-1][prev_inventory] + production_cost + holding_cost
// Update DP
if total_cost < dp[i][final_inventory]:
dp[i][final_inventory] = total_cost
// Find minimum cost to satisfy all demand
min_cost = infinity
for j = 0 to max_inventory:
min_cost = min(min_cost, dp[n][j])
return min_costTime Complexity: O(n·D²) where n is the number of months and D is the total demand. The DP table has size O(n·D), and for each cell we iterate through possible production amounts (O(D) per cell).
Space Complexity: O(n·D) for the DP table. This can be optimized to O(D) by storing only the previous month's row.
Key Insight: The DP state tracks the inventory level at the end of each month. For each month, we determine what production level minimizes cost while satisfying current demand and considering future holding costs.
---
Problem 3: Optimal Matching of Sequences [30 Points]
Part 1 [3 points]
Claim: Every matching contains the pairs (1, 1) and (m, n).
Proof:
By the definition of a matching, we need:
- (Left covered): Every i ∈ [m] must have at least one match in [n]
- (Right covered): Every j ∈ [n] must have at least one match in [m]
- (No crossing): For all i < i' and j < j', we cannot have (i, j') ∈ M and (i', j) ∈ M simultaneously
Consider index 1 ∈ [m]. By the "left covered" property, there exists some j ∈ [n] such that (1, j) ∈ M.
Suppose j > 1. Then consider the rightmost j' < j such that (i, j') ∈ M for some i. If i > 1, then we have 1 < i and j' < j, which would give a crossing with (1, j) and (i, j'), violating the "no crossing" property. Therefore, any match involving indices j' < j must have i = 1. But then we cannot match any other index i > 1 to an index j' < j without creating a crossing.
Since we must cover all indices in [n], the only consistent way is to have (1, 1) ∈ M.
By similar reasoning applied to indices m and n, we must have (m, n) ∈ M.
More formally: If (1, j) ∈ M for j > 1, then no index i > 1 can match to any j' < j (by no-crossing). Since index 1 is covered, all indices i > 1 must match to indices ≥ j. But then indices 2, 3, ..., m (m-1 indices) must all match to the remaining n - j + 1 indices. For this to satisfy "right covered," we need n - j + 1 ≥ m - 1, which may not always hold. The only way to guarantee both conditions is (1, 1) ∈ M. Similarly, (m, n) ∈ M.
---
Part 2 [7 points]
Claim: Let f(m, n) be the number of possible matchings for sequences of length m and n.
Recursive Definition:
$$f(m, n) = \begin{cases} 1 & \text{if } m = 1 \text{ and } n = 1 \\ 0 & \text{if } m \neq n \\ \sum_{k=1}^{m} f(k-1, k-1) \cdot f(m-k, n-k) & \text{if } m = n \geq 2 \end{cases}$$
Explanation:
From Part 1, we know every matching contains (1, 1) and (m, n).
Consider the structure imposed by the "no-crossing" constraint. The pair (1, 1) is always matched. The pair (m, n) is always matched.
Between these two pairs, we can partition the remaining indices into independent subproblems. Specifically:
- Indices {2, 3, ..., k} from X and {2, 3, ..., k} from Y form a matching (not crossing with (k+1, ..., m) and (k+1, ..., n))
- Indices {k+1, ..., m} from X and {k+1, ..., n} from Y form a matching (not crossing with (1, ..., k) and (1, ..., k))
For any valid matching, there exists a "split point" k such that:
- Indices 1 to k from both X and Y form a valid matching
- Indices k+1 to m from both X and Y form a valid matching
- No edges cross between these two partitions
Therefore, for each possible k, the number of matchings is: f(k, k) × f(m-k, n-k)
Summing over all possible split points gives the recursive formula above.
The base case f(1, 1) = 1 represents the single matching {(1, 1)} for sequences of length 1.
---
Part 3 [20 points]
Algorithm: Dynamic Programming for Optimal Matching
Definition:
Let OPT[i][j] = minimum cost of a matching for X[1..i] and Y[1..j]
Key Observation: By the "no-crossing" property and the proof that (1, 1) and (m, n) must be in every matching, the problem has optimal substructure.
Pseudocode:
OPTIMAL-MATCHING(X, Y):
m = length(X)
n = length(Y)
// Ensure m == n (required for valid matching)
if m != n:
return "No valid matching exists"
// Initialize DP table
OPT = new real[m+1][n+1]
for i = 0 to m:
for j = 0 to n:
if i != j:
OPT[i][j] = infinity
else:
OPT[i][j] = 0 if i == 0, else infinity
OPT[0][0] = 0 // Empty matching
// Fill the DP table
for i = 1 to m:
for j = 1 to n:
if i != j:
continue // Only valid when lengths are equal
// The matching must include (i, j)
// We sum cost of matching (i, j) with optimal matchings of X[1..k] and Y[1..k]
// for some k < i, plus optimal matching of X[k+1..i] and Y[k+1..j]
cost_i_j = (X[i] - Y[j])^2
// Try all split points
for k = 0 to i-1:
candidate = OPT[k][k] + cost_i_j + OPT[i-k][j-k]
OPT[i][j] = min(OPT[i][j], candidate)
return OPT[m][n]Correctness:
The algorithm is correct because:
1. Optimal Substructure: If we include pair (i, j) in the optimal matching, then by the no-crossing constraint, the matching must be partitionable into: - A matching of X[1..k] with Y[1..k] for some k < i - The pair (i, j) - A matching of X[k+1..i] with Y[k+1..j] These subproblems are independent and optimal solutions combine to give optimal solution for the full problem.
2. Complete Coverage: Every valid matching must have (1, 1) and (m, n) as established in Part 1. The DP considers all possible ways to build up from (1, 1) to (m, n) using the recursive structure.
3. No-Crossing Compliance: The recursive structure ensures no crossing: if we match X[k+1..i] to Y[k+1..j], these indices form a contiguous block. Since i = j, the structure is symmetric and no crossing occurs.
Time Complexity: O(m³) = O(n³) where m = n is the sequence length. For each of O(m²) DP cells, we iterate through O(m) split points.
Space Complexity: O(m²) = O(n²) for the DP table.
---
Problem 4: Edit Distance & LCS [35 Points]
Part 1 [3 points]
Pseudocode:
EDITDISTANCE(X, Y, c_i, c_d, c_s):
m = length(X)
n = length(Y)
// Initialize DP table
D = new real[m+1][n+1]
// Base case: transforming empty string to Y requires n insertions
D[0][0] = 0
for j = 1 to n:
D[0][j] = D[0][j-1] + c_i
// Base case: transforming X to empty string requires m deletions
for i = 1 to m:
D[i][0] = D[i-1][0] + c_d
// Fill the table using recurrence relation
for i = 1 to m:
for j = 1 to n:
if X[i] == Y[j]:
// Characters match: no operation needed
D[i][j] = D[i-1][j-1]
else:
// Take minimum of three operations:
// 1. Insert Y[j]: D[i][j-1] + c_i
// 2. Delete X[i]: D[i-1][j] + c_d
// 3. Substitute X[i] with Y[j]: D[i-1][j-1] + c_s
D[i][j] = min(
D[i][j-1] + c_i, // Insert
D[i-1][j] + c_d, // Delete
D[i-1][j-1] + c_s // Substitute
)
return D[m][n]---
Part 2 [12 points]
Claim: D(xa, ya) = D(x, y) for all strings x, y and every letter a.
Proof:
Let x be a string of length m and y be a string of length n. Let a be any letter. We want to show D(xa, ya) = D(x, y), where xa denotes the string x concatenated with a.
Definition Recall: D(X, Y) is the minimum cost of any sequence of edit operations that transforms X into Y.
Direction 1: D(xa, ya) ≤ D(x, y)
Let W be an optimal edit sequence transforming x to y with cost D(x, y). We can construct an edit sequence W' that transforms xa to ya as follows:
- Apply the same operations as W to transform x to y
- The final character a in xa becomes the final character in ya (they match)
- No additional operation is needed for the final character
Thus, cost(W') = cost(W) = D(x, y), which shows D(xa, ya) ≤ D(x, y).
Direction 2: D(x, y) ≤ D(xa, ya)
Let W* be an optimal edit sequence transforming xa to ya with cost D(xa, ya).
Consider the last operation in W*:
- If W* ends with a delete of a from xa, then the sequence before this operation transforms xa to y. This sequence has cost D(xa, ya) - c_d. But this also transforms x to y (since removing a from xa leaves x). Therefore, D(x, y) ≤ D(xa, ya) - c_d < D(xa, ya).
- If W* ends with a substitution of a to something other than a, then the final character of ya is not a. But ya ends with a by definition, contradiction. So this case is impossible.
- If W* ends with an insertion, then before this insertion, we have transformed xa to y[1..n-1]. But y[1..n-1] has length n-1 and ya has length n, so the insertion adds the n-th character. Since both y and ya end with a, this insertion must insert a. So the state before the insertion is y[1..n-1], and we transformed xa to y[1..n-1]. But then we need to reach ya = y[1..n-1]a by inserting a, at cost c_i. So the cost before the insertion is D(xa, ya) - c_i. But the sequence transforming xa to y[1..n-1] also transforms x to y[1..n-1]... wait, let me reconsider.
Let me use a clearer argument:
Clearer proof:
Consider any edit sequence W that transforms xa to ya. Let's analyze this sequence systematically.
Case 1: The last operation in W matches the final a from xa with the final a from ya (by performing no operation on the matching pair).
Then the sequence W, before the final step, transforms x to y. So cost(W) = cost(W') where W' is the edit sequence from x to y represented by W without the final pair. Therefore, D(xa, ya) ≥ D(x, y).
Case 2: The final character a of xa is deleted before reaching ya.
Then, before the deletion, the partial sequence transforms some prefix xa[1..m] = x to some string. After deletion, we need to still reach ya. But ya ends with a, so after deleting a from xa, we need to match the remaining string to y, then somehow produce the final a in ya. This requires an insertion of a at some point. But we can instead directly match x to y (the edit sequence for that) and add a final insertion of a to get from y to ya... Actually, let me think about this differently.
Alternative Approach using Optimality:
Suppose D(xa, ya) < D(x, y). Then there exists an edit sequence W* from xa to ya with cost D(xa, ya) < D(x, y).
Consider the state of the transformation after processing the first m characters of xa (which is x):
- At some point during W*, the first m operations have transformed x to some string Z
- The remaining operations transform Za to ya
- The total cost is cost(W*) = D(xa, ya)
For any such decomposition:
- cost(first m transformations) + cost(remaining transformations) ≥ D(xa, ya)
Now, the remaining transformation must transform Za to ya, which ends with a. If Z = y (the optimal endpoint for transforming x), then the remaining operations must transform ya to ya, which costs 0. This gives cost(first m) = D(x, y), contradicting D(xa, ya) < D(x, y).
If Z ≠ y, then the remaining operations must transform Za to ya. The cost to transform y to ya is at most c_i (one insertion of a), and any path through Z must have cost at least the cost to transform Z to y, plus the cost to transform ya to ya. By optimality properties of edit distance, this is at least the direct cost from y to ya.
Therefore, D(xa, ya) ≥ D(x, y).
Combined with the earlier direction, D(xa, ya) = D(x, y).
---
Part 3 [20 points]
Claim: The length of the longest common subsequence L(X, Y) can be computed as:
L(X, Y) = (m + n) - EDITDISTANCE(X, Y, 1, 1, ∞) / 1More precisely:
L(X, Y) = (m + n - EDITDISTANCE(X, Y, c_i=1, c_d=1, c_s=∞)) / 2Wait, let me reconsider. Actually:
L(X, Y) = (m + n) - EDITDISTANCE(X, Y, c_i=1, c_d=1, c_s=∞)Justification:
The key insight is that finding an LCS can be interpreted as an edit distance problem where:
- c_i = 1 (insert cost)
- c_d = 1 (delete cost)
- c_s = ∞ (substitution cost, which we never want to use)
Proof of Correctness:
Let's define D(X, Y, c_i, c_d, c_s) as the minimum cost edit distance with these specific costs.
An edit sequence from X to Y can be decomposed into:
- m deletions of characters from X (each character is either deleted or kept)
- n insertions of characters into Y (each character is either inserted or kept from the deletions)
- 0 substitutions (since c_s = ∞, we never substitute)
More formally, if we transform X to Y with only insertions and deletions (and no substitutions):
- We delete some characters from X, leaving a subsequence of X
- We insert some characters that don't come from X
- The remaining characters (not deleted and not inserted) must match between X and Y in order
If we restrict to no substitutions (c_s = ∞), an optimal edit sequence must: 1. Keep a common subsequence of X and Y (characters that match in order) 2. Delete characters from X not in this subsequence 3. Insert characters from Y not in this subsequence
Let L = length of LCS(X, Y). Then:
- Characters in LCS: L characters (no operation needed, they match)
- Characters in X not in LCS: m - L characters (must be deleted, cost = m - L)
- Characters in Y not in LCS: n - L characters (must be inserted, cost = n - L)
Total cost = (m - L) × 1 + (n - L) × 1 = m + n - 2L
Therefore: D(X, Y, 1, 1, ∞) = m + n - 2L
Solving for L: L = (m + n - D(X, Y, 1, 1, ∞)) / 2
One-line code:
L = (len(X) + len(Y) - EDITDISTANCE(X, Y, 1, 1, float('inf'))) / 2Or more explicitly:
def LCS_length(X, Y):
return (len(X) + len(Y) - EDITDISTANCE(X, Y, c_i=1, c_d=1, c_s=float('inf'))) / 2Why this is correct:
1. With c_s = ∞, substitutions are never used in an optimal edit sequence 2. Any transformation using only insertions and deletions that achieves minimum cost must preserve the longest common subsequence (deleting non-matching parts from X and inserting non-matching parts from Y) 3. The formula L = (m + n - cost) / 2 correctly recovers the LCS length from the minimum cost
This approach elegantly connects LCS to edit distance through a specific choice of operation costs.
Problem 1: String Merging [15 points]
Part 1
Score: 5/5
Approach: Standard 2D boolean DP table dp[i][j] tracking whether Z[1..i+j] merges X[1..i] and Y[1..j].
Strengths:
- Correct subproblem definition matching the reference
- Correct base cases and recurrence (using 0-indexed notation consistently)
- Correct time complexity O(m·n)
- Clear, well-structured pseudocode
Errors/Gaps: None.
Rubric Breakdown:
- Subproblem definition: 1/1
- Recurrence relation: 2/2
- Base cases: 1/1
- Complexity analysis: 1/1
---
Part 2
Score: 10/10
Approach: 3D DP table dp[i][j][k] where k tracks the exact number of substitution errors used, rather than the reference's 2D table tracking the minimum error count.
Strengths:
- Algorithmically correct — correctly tracks exact error counts and checks if any
k ≤ Kis feasible at the end - Base cases correctly propagate error counts for both X-only and Y-only merges
- Main recurrence correctly distinguishes between matching (no error) and mismatching (error consumed) cases for both X and Y contributions
- Correct complexity analysis: O(m·n·K)
Detailed Feedback: The student uses a different but equally valid formulation compared to the reference. The reference tracks T[i,j] = minimum Hamming distance in a 2D table (O(m·n)), while the student tracks exact error counts in a 3D table (O(m·n·K)). The student's approach is less space-efficient but still polynomial and produces the correct answer. The minor pseudocode issue (dead line char_merged = ...) does not affect correctness.
Rubric Breakdown:
- Subproblem definition: 2/2
- Recurrence with error tracking: 4/4
- Base cases: 2/2
- Complexity analysis: 2/2
---
Problem 2: Inventory Planning [20 Points]
Score: 18/20
Approach: Forward DP with dp[i][j] = minimum cost for months 1 through i, ending with j machines in inventory. This is a valid reformulation of the reference's backward DP.
Strengths:
- Correct state definition: (month, ending inventory level)
- Correct transition: iterates over all feasible (prev_inventory, final_inventory) pairs
- Correct production cost formula:
max(0, units_produced - m) * c - Correct holding cost application:
h(final_inventory) - Correct complexity: O(n·D²)
- Good discussion of space optimization possibility
Errors/Gaps:
- Does not mention storing choices for reconstructing the actual production plan (the reference mentions an auxiliary table
s[1..n, 0..D]) - Does not explicitly address that
h(0) = 0(implied by the problem but worth noting) - The answer should ideally note that the optimal ending inventory at month
nwill be 0 (since holding costs are non-negative and there's no benefit to overproduction)
Rubric Breakdown:
- DP state definition: 5/5
- Recurrence/transitions: 6/7 (minor: no mention of plan reconstruction)
- Base cases: 3/3
- Complexity analysis: 4/5 (minor: bounds on inventory not fully justified)
---
Problem 3: Optimal Matching of Sequences [30 Points]
Part 1
Score: 2/3
Approach: Proof by analysis of the no-crossing constraint.
Strengths:
- Correctly identifies that the no-crossing property is the key constraint
- Correctly argues that if (1, j) ∈ M for j > 1, then no i > 1 can match to j' < j
- Handles both (1,1) and (m,n)
Errors/Gaps:
- The proof is convoluted and doesn't reach a clean contradiction. The reference proof is much simpler: if (1,1) ∉ M, then index 1 in X matches some j > 1, and index 1 in Y matches some i > 1, so (i, 1) and (1, j) cross directly (since 1 < i and 1 < j).
- The student's argument via "n - j + 1 ≥ m - 1 which may not always hold" is weak — this isn't a definitive contradiction, it's a conditional statement that doesn't cover all cases.
Rubric Breakdown:
- Proof for (1,1): 1/1.5 (correct idea but not clean)
- Proof for (m,n): 1/1.5 (symmetric argument accepted)
---
Part 2
Score: 2/7
Approach: Claims f(m, n) = 0 when m ≠ n, and provides a split-point recursion for m = n.
Errors/Gaps:
- Fundamental error: Claims matchings only exist when m = n. This is incorrect — the problem explicitly allows m ≠ n. For example, f(1, 3) = 1 (the single matching {(1,1), (1,2), (1,3)}).
- Incorrect recursion even for m = n: The student's formula f(m,m) = Σ_{k=1}^{m} f(k-1,k-1)·f(m-k,m-k) gives f(2,2) = 2, but the correct answer is f(2,2) = 3 (matchings: {(1,1),(2,2)}, {(1,1),(1,2),(2,2)}, {(1,1),(2,1),(2,2)}).
- The reference solution correctly gives: f(m,n) = f(m-1,n) + f(m,n-1) + f(m-1,n-1) for m,n > 1, with base cases f(1,n) = f(m,1) = 1.
Partial Credit: The student demonstrates understanding that (1,1) and (m,n) must be included (from Part 1) and attempts a decomposition into subproblems.
Rubric Breakdown:
- Base cases: 0.5/2 (f(1,1)=1 correct, but f(m,n)=0 for m≠n is wrong)
- Recursive formula: 0.5/3 (attempt at decomposition, but formula is incorrect)
- Explanation: 1/2 (shows some reasoning about subproblem structure)
---
Part 3
Score: 5/20
Approach: DP with OPT[i][j] restricted to i = j, using split-point decomposition.
Errors/Gaps:
- Inherits the fundamental error from Part 2: restricts to m = n and only fills cells where i = j
- Incorrect recurrence: The split-point decomposition
OPT[k][k] + cost + OPT[i-k][j-k]conflates subproblems —OPT[i-k][j-k]refers to the optimal matching for sequences of length i-k, not for the specific subsequences X[k+1..i] and Y[k+1..j] (the costs depend on actual values). - Misses the correct structure: The reference solution's elegant recurrence opt(i,j) = min{opt(i-1,j), opt(i,j-1), opt(i-1,j-1)} + (x_i - y_j)² correctly handles all cases by recognizing that after including (i,j), we remove i from X, j from Y, or both.
- Time complexity claim of O(m³): Consistent with the student's (incorrect) algorithm but the reference achieves O(m·n).
Partial Credit: The student correctly identifies that DP is the right approach, uses the fact that (m,n) must be in every matching, and provides a complexity analysis for their algorithm.
Rubric Breakdown:
- DP table definition: 2/5 (correct idea but wrong restriction to i=j)
- Recurrence: 1/8 (attempt but fundamentally flawed)
- Correctness argument: 1/4 (mentions optimal substructure but based on wrong decomposition)
- Complexity analysis: 1/3 (consistent with their algorithm)
---
Problem 4: Edit Distance & LCS [35 Points]
Part 1
Score: 3/3
Approach: Standard DP implementation of edit distance with parameterized costs.
Strengths:
- Correct base cases: D[0][j] = j·c_i, D[i][0] = i·c_d
- Correct recurrence: handles match (free copy), mismatch (min of insert, delete, substitute)
- Clean, well-structured pseudocode matching the reference
Rubric Breakdown:
- Base cases: 1/1
- Recurrence: 1.5/1.5
- Overall correctness: 0.5/0.5
---
Part 2
Score: 7/12
Approach: Two-direction proof of D(xa, ya) = D(x, y).
Strengths:
- Direction 1 (≤) is correct and clean: appends copy(a→a) to optimal sequence for (x,y)
- Correctly identifies that both directions are needed
- Attempts case analysis on the last operation for Direction 2
Errors/Gaps:
- Direction 2 (≥) is incomplete and confused. The student tries multiple approaches:
1. First attempt: case analysis on last operation gets tangled (confuses "transforms xa to y" with "transforms x to y" in the deletion case) 2. "Alternative Approach": the claim about decomposing edit sequences into "first m characters" is not well-defined — edit operations don't process characters sequentially in a fixed order 3. The argument "If Z = y, then remaining operations transform ya to ya, cost 0" is circular
- The reference proof elegantly handles Direction 2 by showing any optimal sequence for (xa,ya) can be rearranged to end with copy(a→a), reducing to a sequence for (x,y)
Rubric Breakdown:
- Direction 1 (D(xa,ya) ≤ D(x,y)): 4/4
- Direction 2 (D(x,y) ≤ D(xa,ya)): 3/8 (correct setup, identifies relevant cases, but proof is incomplete and the alternative approach is not rigorous)
---
Part 3
Score: 16/20
Approach: Shows L(X,Y) = (|X| + |Y| - EDITDISTANCE(X, Y, 1, 1, ∞)) / 2.
Strengths:
- Correct final formula matching the reference
- Correct identification of parameters: c_i = 1, c_d = 1, c_s = ∞
- Good intuitive argument: with c_s = ∞, only insertions and deletions are used; the optimal sequence preserves the LCS
- Correct algebraic derivation: D = (m - L) + (n - L) = m + n - 2L → L = (m + n - D) / 2
- Provides clean one-line code
Errors/Gaps:
- Initial confusion: first writes the wrong formula L = (m+n) - D (missing the /2), then corrects it. This "Wait, let me reconsider" pattern suggests uncertainty.
- The proof lacks the formal rigor of the reference's inductive argument. The student states that "an optimal edit sequence must keep a common subsequence" and "the minimum cost is achieved when the preserved subsequence is the LCS" but doesn't formally prove these claims via the recursive definitions.
- Does not explicitly use the result from Part 2 (D(xa,ya) = D(x,y)) in the proof, which the reference leverages for the inductive case.
Rubric Breakdown:
- Correct formula/one-line code: 4/4
- Identification of cost parameters: 2/2
- Proof that D = m + n - 2L: 8/12 (correct argument but lacks formal inductive structure)
- Connection to Part 2: 2/2 (implicit use)
---
Overall Summary
| Problem | Score | Notes |
|---|---|---|
| 1 (String Merging) | 15/15 | Excellent — both parts correct with clear pseudocode |
| 2 (Inventory Planning) | 18/20 | Correct forward DP formulation, minor presentation gaps |
| 3 (Optimal Matching) | 9/30 | Fundamental error assuming m = n; cascading issues in Parts 2 & 3 |
| 4 (Edit Distance & LCS) | 26/35 | Strong Part 1 & 3; Part 2 proof incomplete for ≥ direction |
| Total | 68/100 |
General Comments:
The student demonstrates strong competency with standard DP formulations (Problems 1, 2, and Problem 4 Part 1). The edit distance pseudocode and string merging solutions are excellent. The main weakness is in Problem 3, where the incorrect assumption that valid matchings require m = n fundamentally undermines Parts 2 and 3 — this likely stems from a misunderstanding of the matching definition (which allows many-to-one pairings). In Problem 4 Part 2, the student correctly identifies the proof structure but struggles to complete the harder direction rigorously. Strengthening proof technique — particularly for inequality arguments and case analysis — would significantly improve performance.
Total: 68/100
diff --git a/writeup.md b/writeup.md
index 5119c04..8b51f34 100644
--- a/writeup.md
+++ b/writeup.md
@@ -2,8 +2,527 @@
## Student Responses
-<!-- Write your solutions below. Add sections for each problem as needed. -->
-<!-- Use proper mathematical notation where applicable. -->
+---
-[Your solutions here]
+## Problem 1: String Merging [15 points]
+### Part 1 [5 points]
+
+**Algorithm:** String Merging Decision DP
+
+We use a 2D DP table `dp[i][j]` to determine if we can form the first `i + j` characters of Z by merging the first `i` characters of X and the first `j` characters of Y.
+
+**Pseudocode:**
+```
+MERGE-DP(X, Y, Z):
+ m = length(X)
+ n = length(Y)
+
+ // Initialize DP table
+ dp = new boolean[m+1][n+1]
+ dp[0][0] = true
+
+ // Base cases: merging empty string with X
+ for i = 1 to m:
+ if Z[i-1] == X[i-1]:
+ dp[i][0] = dp[i-1][0]
+
+ // Base cases: merging empty string with Y
+ for j = 1 to n:
+ if Z[j-1] == Y[j-1]:
+ dp[0][j] = dp[0][j-1]
+
+ // Fill the table
+ for i = 1 to m:
+ for j = 1 to n:
+ // Try taking character from X
+ if X[i-1] == Z[i+j-1] and dp[i-1][j]:
+ dp[i][j] = true
+ // Try taking character from Y
+ if Y[j-1] == Z[i+j-1] and dp[i][j-1]:
+ dp[i][j] = true
+
+ return dp[m][n]
+```
+
+**Time Complexity:** O(m·n) where m = |X| and n = |Y|. We fill an (m+1) × (n+1) table with constant work per cell.
+
+**Space Complexity:** O(m·n) for the DP table.
+
+---
+
+### Part 2 [10 points]
+
+**Algorithm:** String Merging with Hamming Distance Errors
+
+We modify the DP to track the minimum number of errors (substitutions) needed to merge X and Y into a string that has Hamming distance ≤ K from Z.
+
+Let `dp[i][j][k]` be a boolean indicating whether we can form the first `i + j` characters of some string Z' by merging the first `i` characters of X and the first `j` characters of Y, using exactly k errors (substitutions).
+
+**Pseudocode:**
+```
+MERGE-WITH-ERRORS(X, Y, Z, K):
+ m = length(X)
+ n = length(Y)
+
+ // Initialize DP table
+ dp = new boolean[m+1][n+1][K+1]
+ dp[0][0][0] = true
+
+ // Base cases: merging X with empty Y
+ for i = 1 to m:
+ for k = 0 to K:
+ if k == 0:
+ if Z[i-1] == X[i-1] and dp[i-1][0][0]:
+ dp[i][0][0] = true
+ else:
+ // Use a substitution error
+ if dp[i-1][0][k-1]:
+ dp[i][0][k] = true
+ // No error needed
+ if Z[i-1] == X[i-1] and dp[i-1][0][k]:
+ dp[i][0][k] = true
+
+ // Base cases: merging Y with empty X
+ for j = 1 to n:
+ for k = 0 to K:
+ if k == 0:
+ if Z[j-1] == Y[j-1] and dp[0][j-1][0]:
+ dp[0][j][0] = true
+ else:
+ // Use a substitution error
+ if dp[0][j-1][k-1]:
+ dp[0][j][k] = true
+ // No error needed
+ if Z[j-1] == Y[j-1] and dp[0][j-1][k]:
+ dp[0][j][k] = true
+
+ // Fill the table
+ for i = 1 to m:
+ for j = 1 to n:
+ for k = 0 to K:
+ char_merged = the next character should be X[i-1] or Y[j-1]
+
+ // Try taking character from X
+ if X[i-1] == Z[i+j-1]:
+ // No error needed
+ if dp[i-1][j][k]:
+ dp[i][j][k] = true
+ else:
+ // Error needed (substitution)
+ if k > 0 and dp[i-1][j][k-1]:
+ dp[i][j][k] = true
+
+ // Try taking character from Y
+ if Y[j-1] == Z[i+j-1]:
+ // No error needed
+ if dp[i][j-1][k]:
+ dp[i][j][k] = true
+ else:
+ // Error needed (substitution)
+ if k > 0 and dp[i][j-1][k-1]:
+ dp[i][j][k] = true
+
+ // Check if we can merge with at most K errors
+ for k = 0 to K:
+ if dp[m][n][k]:
+ return true
+ return false
+```
+
+**Time Complexity:** O(m·n·K) where m = |X|, n = |Y|, and K is the maximum number of errors allowed. We fill an (m+1) × (n+1) × (K+1) table with constant work per cell.
+
+**Space Complexity:** O(m·n·K) for the 3D DP table. This can be optimized to O(n·K) using rolling arrays if needed.
+
+---
+
+## Problem 2: Inventory Planning [20 Points]
+
+**Problem Setup:**
+- n months with known demand d₁, d₂, ..., dₙ
+- Total demand D = Σdᵢ
+- Regular production capacity: m machines/month (no cost)
+- Overtime production: c dollars per machine (above m)
+- Inventory holding cost: h(j) for holding j machines (monotonically increasing)
+- Goal: Minimize total cost while meeting all demand
+
+**Algorithm:** Dynamic Programming for Inventory Planning
+
+Let `dp[i][j]` be the minimum cost to meet demand for months 1 through i, ending with j machines in inventory at the end of month i.
+
+**Pseudocode:**
+```
+INVENTORY-PLAN(n, d[], m, c, h[]):
+ // Precompute maximum possible inventory at any point
+ max_inventory = sum of all demands
+
+ // Initialize DP table
+ dp = new integer[n+1][max_inventory+1]
+ for j = 0 to max_inventory:
+ dp[0][j] = infinity
+ dp[0][0] = 0 // Start with no inventory
+
+ // Process each month
+ for i = 1 to n:
+ for prev_inventory = 0 to max_inventory:
+ if dp[i-1][prev_inventory] == infinity:
+ continue
+
+ // We must satisfy demand d[i]
+ // We have prev_inventory from previous month
+ // We need to produce: units_needed
+
+ for final_inventory = 0 to max_inventory:
+ units_produced = d[i] + final_inventory - prev_inventory
+
+ // Check feasibility
+ if units_produced < 0:
+ continue // Can't reduce production below 0
+
+ // Calculate cost for this month
+ if units_produced <= m:
+ production_cost = 0 // Within regular capacity
+ else:
+ overtime_units = units_produced - m
+ production_cost = overtime_units * c
+
+ holding_cost = h[final_inventory] // Cost to hold final_inventory machines
+
+ total_cost = dp[i-1][prev_inventory] + production_cost + holding_cost
+
+ // Update DP
+ if total_cost < dp[i][final_inventory]:
+ dp[i][final_inventory] = total_cost
+
+ // Find minimum cost to satisfy all demand
+ min_cost = infinity
+ for j = 0 to max_inventory:
+ min_cost = min(min_cost, dp[n][j])
+
+ return min_cost
+```
+
+**Time Complexity:** O(n·D²) where n is the number of months and D is the total demand. The DP table has size O(n·D), and for each cell we iterate through possible production amounts (O(D) per cell).
+
+**Space Complexity:** O(n·D) for the DP table. This can be optimized to O(D) by storing only the previous month's row.
+
+**Key Insight:** The DP state tracks the inventory level at the end of each month. For each month, we determine what production level minimizes cost while satisfying current demand and considering future holding costs.
+
+---
+
+## Problem 3: Optimal Matching of Sequences [30 Points]
+
+### Part 1 [3 points]
+
+**Claim:** Every matching contains the pairs (1, 1) and (m, n).
+
+**Proof:**
+
+By the definition of a matching, we need:
+- **(Left covered):** Every i ∈ [m] must have at least one match in [n]
+- **(Right covered):** Every j ∈ [n] must have at least one match in [m]
+- **(No crossing):** For all i < i' and j < j', we cannot have (i, j') ∈ M and (i', j) ∈ M simultaneously
+
+Consider index 1 ∈ [m]. By the "left covered" property, there exists some j ∈ [n] such that (1, j) ∈ M.
+
+Suppose j > 1. Then consider the rightmost j' < j such that (i, j') ∈ M for some i. If i > 1, then we have 1 < i and j' < j, which would give a crossing with (1, j) and (i, j'), violating the "no crossing" property. Therefore, any match involving indices j' < j must have i = 1. But then we cannot match any other index i > 1 to an index j' < j without creating a crossing.
+
+Since we must cover all indices in [n], the only consistent way is to have (1, 1) ∈ M.
+
+By similar reasoning applied to indices m and n, we must have (m, n) ∈ M.
+
+More formally: If (1, j) ∈ M for j > 1, then no index i > 1 can match to any j' < j (by no-crossing). Since index 1 is covered, all indices i > 1 must match to indices ≥ j. But then indices 2, 3, ..., m (m-1 indices) must all match to the remaining n - j + 1 indices. For this to satisfy "right covered," we need n - j + 1 ≥ m - 1, which may not always hold. The only way to guarantee both conditions is (1, 1) ∈ M. Similarly, (m, n) ∈ M.
+
+---
+
+### Part 2 [7 points]
+
+**Claim:** Let f(m, n) be the number of possible matchings for sequences of length m and n.
+
+**Recursive Definition:**
+
+$$f(m, n) = \begin{cases}
+1 & \text{if } m = 1 \text{ and } n = 1 \\
+0 & \text{if } m \neq n \\
+\sum_{k=1}^{m} f(k-1, k-1) \cdot f(m-k, n-k) & \text{if } m = n \geq 2
+\end{cases}$$
+
+**Explanation:**
+
+From Part 1, we know every matching contains (1, 1) and (m, n).
+
+Consider the structure imposed by the "no-crossing" constraint. The pair (1, 1) is always matched. The pair (m, n) is always matched.
+
+Between these two pairs, we can partition the remaining indices into independent subproblems. Specifically:
+- Indices {2, 3, ..., k} from X and {2, 3, ..., k} from Y form a matching (not crossing with (k+1, ..., m) and (k+1, ..., n))
+- Indices {k+1, ..., m} from X and {k+1, ..., n} from Y form a matching (not crossing with (1, ..., k) and (1, ..., k))
+
+For any valid matching, there exists a "split point" k such that:
+- Indices 1 to k from both X and Y form a valid matching
+- Indices k+1 to m from both X and Y form a valid matching
+- No edges cross between these two partitions
+
+Therefore, for each possible k, the number of matchings is:
+f(k, k) × f(m-k, n-k)
+
+Summing over all possible split points gives the recursive formula above.
+
+The base case f(1, 1) = 1 represents the single matching {(1, 1)} for sequences of length 1.
+
+---
+
+### Part 3 [20 points]
+
+**Algorithm:** Dynamic Programming for Optimal Matching
+
+**Definition:**
+Let `OPT[i][j]` = minimum cost of a matching for X[1..i] and Y[1..j]
+
+**Key Observation:** By the "no-crossing" property and the proof that (1, 1) and (m, n) must be in every matching, the problem has optimal substructure.
+
+**Pseudocode:**
+```
+OPTIMAL-MATCHING(X, Y):
+ m = length(X)
+ n = length(Y)
+
+ // Ensure m == n (required for valid matching)
+ if m != n:
+ return "No valid matching exists"
+
+ // Initialize DP table
+ OPT = new real[m+1][n+1]
+ for i = 0 to m:
+ for j = 0 to n:
+ if i != j:
+ OPT[i][j] = infinity
+ else:
+ OPT[i][j] = 0 if i == 0, else infinity
+
+ OPT[0][0] = 0 // Empty matching
+
+ // Fill the DP table
+ for i = 1 to m:
+ for j = 1 to n:
+ if i != j:
+ continue // Only valid when lengths are equal
+
+ // The matching must include (i, j)
+ // We sum cost of matching (i, j) with optimal matchings of X[1..k] and Y[1..k]
+ // for some k < i, plus optimal matching of X[k+1..i] and Y[k+1..j]
+
+ cost_i_j = (X[i] - Y[j])^2
+
+ // Try all split points
+ for k = 0 to i-1:
+ candidate = OPT[k][k] + cost_i_j + OPT[i-k][j-k]
+ OPT[i][j] = min(OPT[i][j], candidate)
+
+ return OPT[m][n]
+```
+
+**Correctness:**
+
+The algorithm is correct because:
+
+1. **Optimal Substructure:** If we include pair (i, j) in the optimal matching, then by the no-crossing constraint, the matching must be partitionable into:
+ - A matching of X[1..k] with Y[1..k] for some k < i
+ - The pair (i, j)
+ - A matching of X[k+1..i] with Y[k+1..j]
+ These subproblems are independent and optimal solutions combine to give optimal solution for the full problem.
+
+2. **Complete Coverage:** Every valid matching must have (1, 1) and (m, n) as established in Part 1. The DP considers all possible ways to build up from (1, 1) to (m, n) using the recursive structure.
+
+3. **No-Crossing Compliance:** The recursive structure ensures no crossing: if we match X[k+1..i] to Y[k+1..j], these indices form a contiguous block. Since i = j, the structure is symmetric and no crossing occurs.
+
+**Time Complexity:** O(m³) = O(n³) where m = n is the sequence length. For each of O(m²) DP cells, we iterate through O(m) split points.
+
+**Space Complexity:** O(m²) = O(n²) for the DP table.
+
+---
+
+## Problem 4: Edit Distance & LCS [35 Points]
+
+### Part 1 [3 points]
+
+**Pseudocode:**
+```
+EDITDISTANCE(X, Y, c_i, c_d, c_s):
+ m = length(X)
+ n = length(Y)
+
+ // Initialize DP table
+ D = new real[m+1][n+1]
+
+ // Base case: transforming empty string to Y requires n insertions
+ D[0][0] = 0
+ for j = 1 to n:
+ D[0][j] = D[0][j-1] + c_i
+
+ // Base case: transforming X to empty string requires m deletions
+ for i = 1 to m:
+ D[i][0] = D[i-1][0] + c_d
+
+ // Fill the table using recurrence relation
+ for i = 1 to m:
+ for j = 1 to n:
+ if X[i] == Y[j]:
+ // Characters match: no operation needed
+ D[i][j] = D[i-1][j-1]
+ else:
+ // Take minimum of three operations:
+ // 1. Insert Y[j]: D[i][j-1] + c_i
+ // 2. Delete X[i]: D[i-1][j] + c_d
+ // 3. Substitute X[i] with Y[j]: D[i-1][j-1] + c_s
+ D[i][j] = min(
+ D[i][j-1] + c_i, // Insert
+ D[i-1][j] + c_d, // Delete
+ D[i-1][j-1] + c_s // Substitute
+ )
+
+ return D[m][n]
+```
+
+---
+
+### Part 2 [12 points]
+
+**Claim:** D(xa, ya) = D(x, y) for all strings x, y and every letter a.
+
+**Proof:**
+
+Let x be a string of length m and y be a string of length n. Let a be any letter. We want to show D(xa, ya) = D(x, y), where xa denotes the string x concatenated with a.
+
+**Definition Recall:** D(X, Y) is the minimum cost of any sequence of edit operations that transforms X into Y.
+
+**Direction 1: D(xa, ya) ≤ D(x, y)**
+
+Let W be an optimal edit sequence transforming x to y with cost D(x, y). We can construct an edit sequence W' that transforms xa to ya as follows:
+- Apply the same operations as W to transform x to y
+- The final character a in xa becomes the final character in ya (they match)
+- No additional operation is needed for the final character
+
+Thus, cost(W') = cost(W) = D(x, y), which shows D(xa, ya) ≤ D(x, y).
+
+**Direction 2: D(x, y) ≤ D(xa, ya)**
+
+Let W* be an optimal edit sequence transforming xa to ya with cost D(xa, ya).
+
+Consider the last operation in W*:
+- If W* ends with a delete of a from xa, then the sequence before this operation transforms xa to y. This sequence has cost D(xa, ya) - c_d. But this also transforms x to y (since removing a from xa leaves x). Therefore, D(x, y) ≤ D(xa, ya) - c_d < D(xa, ya).
+
+- If W* ends with a substitution of a to something other than a, then the final character of ya is not a. But ya ends with a by definition, contradiction. So this case is impossible.
+
+- If W* ends with an insertion, then before this insertion, we have transformed xa to y[1..n-1]. But y[1..n-1] has length n-1 and ya has length n, so the insertion adds the n-th character. Since both y and ya end with a, this insertion must insert a. So the state before the insertion is y[1..n-1], and we transformed xa to y[1..n-1]. But then we need to reach ya = y[1..n-1]a by inserting a, at cost c_i. So the cost before the insertion is D(xa, ya) - c_i. But the sequence transforming xa to y[1..n-1] also transforms x to y[1..n-1]... wait, let me reconsider.
+
+Let me use a clearer argument:
+
+**Clearer proof:**
+
+Consider any edit sequence W that transforms xa to ya. Let's analyze this sequence systematically.
+
+**Case 1:** The last operation in W matches the final a from xa with the final a from ya (by performing no operation on the matching pair).
+
+Then the sequence W, before the final step, transforms x to y. So cost(W) = cost(W') where W' is the edit sequence from x to y represented by W without the final pair. Therefore, D(xa, ya) ≥ D(x, y).
+
+**Case 2:** The final character a of xa is deleted before reaching ya.
+
+Then, before the deletion, the partial sequence transforms some prefix xa[1..m] = x to some string. After deletion, we need to still reach ya. But ya ends with a, so after deleting a from xa, we need to match the remaining string to y, then somehow produce the final a in ya. This requires an insertion of a at some point. But we can instead directly match x to y (the edit sequence for that) and add a final insertion of a to get from y to ya... Actually, let me think about this differently.
+
+**Alternative Approach using Optimality:**
+
+Suppose D(xa, ya) < D(x, y). Then there exists an edit sequence W* from xa to ya with cost D(xa, ya) < D(x, y).
+
+Consider the state of the transformation after processing the first m characters of xa (which is x):
+- At some point during W*, the first m operations have transformed x to some string Z
+- The remaining operations transform Za to ya
+- The total cost is cost(W*) = D(xa, ya)
+
+For any such decomposition:
+- cost(first m transformations) + cost(remaining transformations) ≥ D(xa, ya)
+
+Now, the remaining transformation must transform Za to ya, which ends with a. If Z = y (the optimal endpoint for transforming x), then the remaining operations must transform ya to ya, which costs 0. This gives cost(first m) = D(x, y), contradicting D(xa, ya) < D(x, y).
+
+If Z ≠ y, then the remaining operations must transform Za to ya. The cost to transform y to ya is at most c_i (one insertion of a), and any path through Z must have cost at least the cost to transform Z to y, plus the cost to transform ya to ya. By optimality properties of edit distance, this is at least the direct cost from y to ya.
+
+Therefore, D(xa, ya) ≥ D(x, y).
+
+Combined with the earlier direction, D(xa, ya) = D(x, y).
+
+---
+
+### Part 3 [20 points]
+
+**Claim:** The length of the longest common subsequence L(X, Y) can be computed as:
+```
+L(X, Y) = (m + n) - EDITDISTANCE(X, Y, 1, 1, ∞) / 1
+```
+
+More precisely:
+```
+L(X, Y) = (m + n - EDITDISTANCE(X, Y, c_i=1, c_d=1, c_s=∞)) / 2
+```
+
+Wait, let me reconsider. Actually:
+
+```
+L(X, Y) = (m + n) - EDITDISTANCE(X, Y, c_i=1, c_d=1, c_s=∞)
+```
+
+**Justification:**
+
+The key insight is that finding an LCS can be interpreted as an edit distance problem where:
+- c_i = 1 (insert cost)
+- c_d = 1 (delete cost)
+- c_s = ∞ (substitution cost, which we never want to use)
+
+**Proof of Correctness:**
+
+Let's define D(X, Y, c_i, c_d, c_s) as the minimum cost edit distance with these specific costs.
+
+An edit sequence from X to Y can be decomposed into:
+- m deletions of characters from X (each character is either deleted or kept)
+- n insertions of characters into Y (each character is either inserted or kept from the deletions)
+- 0 substitutions (since c_s = ∞, we never substitute)
+
+More formally, if we transform X to Y with only insertions and deletions (and no substitutions):
+- We delete some characters from X, leaving a subsequence of X
+- We insert some characters that don't come from X
+- The remaining characters (not deleted and not inserted) must match between X and Y in order
+
+If we restrict to no substitutions (c_s = ∞), an optimal edit sequence must:
+1. Keep a common subsequence of X and Y (characters that match in order)
+2. Delete characters from X not in this subsequence
+3. Insert characters from Y not in this subsequence
+
+Let L = length of LCS(X, Y). Then:
+- Characters in LCS: L characters (no operation needed, they match)
+- Characters in X not in LCS: m - L characters (must be deleted, cost = m - L)
+- Characters in Y not in LCS: n - L characters (must be inserted, cost = n - L)
+
+Total cost = (m - L) × 1 + (n - L) × 1 = m + n - 2L
+
+Therefore:
+D(X, Y, 1, 1, ∞) = m + n - 2L
+
+Solving for L:
+L = (m + n - D(X, Y, 1, 1, ∞)) / 2
+
+**One-line code:**
+```python
+L = (len(X) + len(Y) - EDITDISTANCE(X, Y, 1, 1, float('inf'))) / 2
+```
+
+Or more explicitly:
+```python
+def LCS_length(X, Y):
+ return (len(X) + len(Y) - EDITDISTANCE(X, Y, c_i=1, c_d=1, c_s=float('inf'))) / 2
+```
+
+**Why this is correct:**
+
+1. With c_s = ∞, substitutions are never used in an optimal edit sequence
+2. Any transformation using only insertions and deletions that achieves minimum cost must preserve the longest common subsequence (deleting non-matching parts from X and inserting non-matching parts from Y)
+3. The formula L = (m + n - cost) / 2 correctly recovers the LCS length from the minimum cost
+
+This approach elegantly connects LCS to edit distance through a specific choice of operation costs.
Sub-Model Usage
| Model | Input | Output | Cache Read | Cost |
|---|---|---|---|---|
| claude-haiku-4-5-20251001 | 30 | 8.5K | 117.3K | $0.08 |
BSCS Bench