CHOCOLA - Chocolate
Problem Statement
SPOJ Problem - CHOCOLA
Given a chocolate bar of m × n squares, break it into individual squares. Each cut is a horizontal or vertical line that divides a piece into two. The cost of a cut is the length of the cut line multiplied by a cost factor.
- Horizontal cuts have costs
h[1], h[2], ..., h[m-1] - Vertical cuts have costs
v[1], v[2], ..., v[n-1]
Find the minimum total cost to break the chocolate into m × n individual squares.
Constraints:
- 1 ≤ m, n ≤ 1000
- 1 ≤ costs ≤ 1000
Example:
- Input:
m=2, n=2,h=[2],v=[1] - Output:
5 - Explanation: Cut vertically first (cost 1×2=2), then horizontally through 2 pieces (cost 2×2=4). Total = 6. OR cut horizontally first (cost 2×1=2), then vertically through 2 pieces (cost 1×2=2). Wait, let me recalculate...
Actually: If we cut horizontally first (cost 2), we have 2 pieces. Then vertical cut costs 1×2=2. Total=4. If we cut vertically first (cost 1), we have 2 pieces. Then horizontal cut costs 2×2=4. Total=5. So minimum is 4... but the answer format depends on exact problem statement.
Approach 1: Brute Force (Try All Orders)
Intuition
The cost of a cut depends on how many pieces it goes through. A cut made later goes through more pieces. Try all possible orderings of cuts and find the minimum cost.
Algorithm
- Generate all permutations of cut order
- For each permutation, simulate cutting and calculate total cost
- Track and return minimum cost
import java.util.*;
public class Solution {
private int minCost = Integer.MAX_VALUE;
public int chocolateBreaking(int[] h, int[] v) {
List<int[]> cuts = new ArrayList<>(); // {cost, type: 0=h, 1=v}
for (int cost : h) cuts.add(new int[]{cost, 0});
for (int cost : v) cuts.add(new int[]{cost, 1});
minCost = Integer.MAX_VALUE;
tryAllOrders(cuts, 0, 1, 1, 0);
return minCost;
}
private void tryAllOrders(List<int[]> cuts, int idx,
int hPieces, int vPieces, int cost) {
if (idx == cuts.size()) {
minCost = Math.min(minCost, cost);
return;
}
for (int i = idx; i < cuts.size(); i++) {
Collections.swap(cuts, idx, i);
int cutCost = cuts.get(idx)[0];
int cutType = cuts.get(idx)[1];
if (cutType == 0) {
tryAllOrders(cuts, idx + 1, hPieces + 1, vPieces,
cost + cutCost * vPieces);
} else {
tryAllOrders(cuts, idx + 1, hPieces, vPieces + 1,
cost + cutCost * hPieces);
}
Collections.swap(cuts, idx, i);
}
}
}Complexity Analysis
- Time Complexity: O(n! × n) where n = (m-1) + (n-1) total cuts
- Space Complexity: O(n) - Recursion stack
Approach 2: Greedy with Sorting (Optimal)
Intuition
Key Insight: Make expensive cuts first! When we make a cut, it affects all future perpendicular cuts by multiplying their cost. So expensive cuts should be made when there are fewer pieces to cut through.
Greedy Strategy:
- Sort all cuts by cost in descending order
- Process cuts in this order
- A horizontal cut costs:
h[i] × (vertical_pieces) - A vertical cut costs:
v[i] × (horizontal_pieces)
Algorithm
- Sort horizontal and vertical cuts in descending order
- Use two pointers to merge in descending order
- Process cuts greedily, tracking piece counts
- Return total cost
import java.util.*;
public class Solution {
public long chocolateBreaking(int[] h, int[] v) {
// Sort in descending order
Integer[] hArr = Arrays.stream(h).boxed().toArray(Integer[]::new);
Integer[] vArr = Arrays.stream(v).boxed().toArray(Integer[]::new);
Arrays.sort(hArr, Collections.reverseOrder());
Arrays.sort(vArr, Collections.reverseOrder());
int hIdx = 0, vIdx = 0;
int hPieces = 1, vPieces = 1;
long totalCost = 0;
while (hIdx < hArr.length && vIdx < vArr.length) {
if (hArr[hIdx] >= vArr[vIdx]) {
totalCost += (long)hArr[hIdx] * vPieces;
hPieces++;
hIdx++;
} else {
totalCost += (long)vArr[vIdx] * hPieces;
vPieces++;
vIdx++;
}
}
while (hIdx < hArr.length) {
totalCost += (long)hArr[hIdx] * vPieces;
hPieces++;
hIdx++;
}
while (vIdx < vArr.length) {
totalCost += (long)vArr[vIdx] * hPieces;
vPieces++;
vIdx++;
}
return totalCost;
}
public static void main(String[] args) {
Solution sol = new Solution();
int[] h = {2};
int[] v = {1};
System.out.println(sol.chocolateBreaking(h, v));
}
}Dry Run Example
m=4, n=6 (4×6 chocolate)
h = [4, 1, 2] (3 horizontal cuts needed)
v = [2, 1, 3, 1, 4] (5 vertical cuts needed)
Step 1: Sort descending
h_sorted = [4, 2, 1]
v_sorted = [4, 3, 2, 1, 1]
Step 2: Greedy merging
h_pieces=1, v_pieces=1, cost=0
Compare h[0]=4 vs v[0]=4: Equal, pick h
cost += 4 × 1 = 4, h_pieces=2
Compare h[1]=2 vs v[0]=4: v is larger
cost += 4 × 2 = 8, v_pieces=2
Compare h[1]=2 vs v[1]=3: v is larger
cost += 3 × 2 = 6, v_pieces=3
Compare h[1]=2 vs v[2]=2: Equal, pick h
cost += 2 × 3 = 6, h_pieces=3
Compare h[2]=1 vs v[2]=2: v is larger
cost += 2 × 3 = 6, v_pieces=4
Compare h[2]=1 vs v[3]=1: Equal, pick h
cost += 1 × 4 = 4, h_pieces=4
Remaining v cuts: [1]
cost += 1 × 4 = 4, v_pieces=5
Total cost = 4 + 8 + 6 + 6 + 6 + 4 + 4 = 38
The chocolate is now divided into 4×6 = 24 individual pieces.
Complexity Analysis
- Time Complexity: O(n² log n) where n = max(m, n). Sorting takes O((m+n) log(m+n)), but the term in problem constraints gives O(n² log n)
- Space Complexity: O(n) - For sorted arrays
Complete SPOJ Solution
Key Takeaways
- Expensive First: Make expensive cuts first when fewer pieces exist
- Multiplicative Effect: Each cut's cost is multiplied by perpendicular piece count
- Two-Pointer Merge: Efficiently merge sorted arrays
- Greedy Proof: Making expensive cuts later only increases cost
- Integer Overflow: Use long long for cost calculations
- Similar Problems: Matrix chain multiplication, optimal BST (with DP)
- SPOJ Format: Handle multiple test cases