"Cut rectangular slices of pizza. The pizza is divided into cells. Each slice must contain at least L tomato cells (T) and L mushroom cells (M). Each slice must contain at most H cells [1]. Maximize the total number of cells in the cuts."
In the last post, we solved a 2D slicing problem by optimizing each row independently. We should expect to do better by actually selecting 2D intervals instead. A 2D interval is represented as:
(x_start, x_end, y_start, y_end)
To generalize the code to 2D intervals, we do not have to change much. This is thanks to the fact that we only dealt with intervals in a very abstract sense before: as elements with a size (for the score) and an overlapping relation (to forbid overlapping slices). We need only to change the way we generate the intervals, and the overlapping relation. The code:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def get_area2(c): | |
ix, jx, iy, jy = c | |
for x in range(ix, jx): | |
for y in range(iy, jy): | |
yield (x, y) | |
def interval_overlap2(c0, c1): | |
if any([interval_contains2(c0, p) for p in get_area2(c1)]): | |
return True | |
if any([interval_contains2(c1, p) for p in get_area2(c0)]): | |
return True | |
return False | |
def interval_selection2(mat, feasible, max_width=None): | |
N, M = mat.shape | |
if not max_width: | |
max_width = max(N, M) | |
solver = SugarRush() | |
coord2var = {} # coordinate to variable | |
for ix in range(N): | |
for jx in range(ix, min(ix + max_width, N+1)): | |
for iy in range(M): | |
for jy in range(iy, min(iy + max_width, M+1)): | |
slize = mat[ix:jx, iy:jy] | |
if feasible(slize): | |
coord2var[(ix, jx, iy, jy)] = solver.var() | |
mutex_clauses = [] # mutual exclusion clauses | |
coord_2 = [(c0, c1) for c0 in coord2var for c1 in coord2var] | |
for c0, c1 in coord_2: | |
if c0 > c1: # ignore symmetries | |
continue | |
if c0 == c1: # can't forbid overlap with self | |
continue | |
if interval_overlap2(c0, c1): | |
var0 = coord2var[c0] | |
var1 = coord2var[c1] | |
mutex_clauses.append([-var0, -var1]) | |
solver.add(mutex_clauses) | |
p2covering = {} # point to covering | |
for i in range(N): | |
for j in range(M): | |
p = (i, j) | |
covering = [] | |
for c, var in coord2var.items(): | |
if interval_contains2(c, p): | |
covering.append(var) | |
if covering: | |
p2covering[p] = covering | |
p2covered = {} | |
for p, covering in p2covering.items(): | |
ind, clauses = solver.indicator([covering]) | |
solver.add(clauses) | |
p2covered[p] = ind | |
opt_vars = [-ind for p, ind in p2covered.items()] | |
itot_clauses, itot_vars = solver.itotalizer(opt_vars) | |
solver.add(itot_clauses) | |
best = solver.optimize(itot_vars) | |
if best is None: | |
return [] | |
selected_coords = [coord for coord, var in coord2var.items() | |
if solver.solution_value(var)] | |
return selected_coords |
Now we need a solution strategy. Tests show that the 2D interval selection gets tough (starts taking more than 10 seconds) when solving for larger than 20x20 matrices. However 12x12 matrices can be solved quickly (about 0.6 seconds). Solving with 12x12 sections often give a perfect score on the subproblem:
![]() |
Optimization on 12x12 subproblem. Colored borders mark slices. |
![]() |
Optimization on 12x12 subproblem. Colored borders mark slices. In many cases, almost all slices are just rows or columns. |
Conclusion
This result does not only say something about the power of model-based approaches, but also about the problem itself. It is quite an easy problem where we can get an almost perfect score by just solving independent subproblems.
[1] pizza.pdf
[2] sagishporer/hashcode-practice-pizza
Inga kommentarer:
Skicka en kommentar