1 /* PBN, Paint-By-Numbers Puzzle */
3 /* Written in GNU MathProg by Andrew Makhorin <mao@gnu.org> */
5 /* A paint-by-number puzzle consists of an m*n grid of pixels (the
6 canvas) together with m+n cluster-size sequences, one for each row
7 and column. The goal is to paint the canvas with a picture that
8 satisfies the following constraints:
10 1. Each pixel must be blank or white.
12 2. If a row or column has cluster-size sequence s1, s2, ..., sk,
13 then it must contain k clusters of black pixels - the first with
14 s1 black pixels, the second with s2 black pixels, and so on.
16 It should be noted that "first" means "leftmost" for rows and
17 "topmost" for columns, and that rows and columns need not begin or
18 end with black pixels.
26 3 6 # # # . # # # # # #
27 1 4 # . . . . . # # # #
28 1 1 3 . . # . # . . # # #
30 3 3 . . # # # . . # # #
31 1 4 # . . . . . # # # #
32 2 5 # # . . . # # # # #
33 2 5 # # . . . # # # # #
34 1 1 . . . # . . . . . #
37 (In Russia this sort of puzzles is known as "Japanese crossword".)
40 Robert A. Bosch, "Painting by Numbers", 2000.
41 <http://www.oberlin.edu/~math/faculty/bosch/pbn-page.html> */
43 param m, integer, >= 1;
44 /* the number of rows */
46 param n, integer, >= 1;
47 /* the number of columns */
49 param row{i in 1..m, 1..n div 2}, integer, >= 0, default 0;
50 /* the cluster-size sequence for row i (raw data) */
52 param col{j in 1..n, 1..m div 2}, integer, >= 0, default 0;
53 /* the cluster-size sequence for column j (raw data) */
55 param kr{i in 1..m} := sum{t in 1..n div 2: row[i,t] > 0} 1;
56 /* the number of clusters in row i */
58 param kc{j in 1..n} := sum{t in 1..m div 2: col[j,t] > 0} 1;
59 /* the number of clusters in column j */
61 param sr{i in 1..m, t in 1..kr[i]} := row[i,t], integer, >= 1;
62 /* the cluster-size sequence for row i */
64 param sc{j in 1..n, t in 1..kc[j]} := col[j,t], integer, >= 1;
65 /* the cluster-size sequence for column j */
67 check{i in 1..m}: sum{t in 1..kr[i]} sr[i,t] <= n - (kr[i] - 1);
68 /* check that the sum of the cluster sizes in each row is valid */
70 check{j in 1..n}: sum{t in 1..kc[j]} sc[j,t] <= m - (kc[j] - 1);
71 /* check that the sum of the cluster sizes in each column is valid */
73 check: sum{i in 1..m, t in 1..kr[i]} sr[i,t] =
74 sum{j in 1..n, t in 1..kc[j]} sc[j,t];
75 /* check that the sum of the cluster sizes in all rows is equal to the
76 sum of the cluster sizes in all columns */
78 param er{i in 1..m, t in 1..kr[i]} :=
79 if t = 1 then 1 else er[i,t-1] + sr[i,t-1] + 1;
80 /* the smallest value of j such that row i's t-th cluster can be
81 placed in row i with its leftmost pixel occupying pixel j */
83 param lr{i in 1..m, t in 1..kr[i]} :=
84 if t = kr[i] then n + 1 - sr[i,t] else lr[i,t+1] - sr[i,t] - 1;
85 /* the largest value of j such that row i's t-th cluster can be
86 placed in row i with its leftmost pixel occupying pixel j */
88 param ec{j in 1..n, t in 1..kc[j]} :=
89 if t = 1 then 1 else ec[j,t-1] + sc[j,t-1] + 1;
90 /* the smallest value of i such that column j's t-th cluster can be
91 placed in column j with its topmost pixel occupying pixel i */
93 param lc{j in 1..n, t in 1..kc[j]} :=
94 if t = kc[j] then m + 1 - sc[j,t] else lc[j,t+1] - sc[j,t] - 1;
95 /* the largest value of i such that column j's t-th cluster can be
96 placed in column j with its topmost pixel occupying pixel i */
98 var z{i in 1..m, j in 1..n}, binary;
99 /* z[i,j] = 1, if row i's j-th pixel is painted black
100 z[i,j] = 0, if row i's j-th pixel is painted white */
102 var y{i in 1..m, t in 1..kr[i], j in er[i,t]..lr[i,t]}, binary;
103 /* y[i,t,j] = 1, if row i's t-th cluster is placed in row i with its
104 leftmost pixel occupying pixel j
105 y[i,t,j] = 0, if not */
107 var x{j in 1..n, t in 1..kc[j], i in ec[j,t]..lc[j,t]}, binary;
108 /* x[j,t,i] = 1, if column j's t-th cluster is placed in column j with
109 its topmost pixel occupying pixel i
110 x[j,t,i] = 0, if not */
112 s.t. fa{i in 1..m, t in 1..kr[i]}:
113 sum{j in er[i,t]..lr[i,t]} y[i,t,j] = 1;
114 /* row i's t-th cluster must appear in row i exactly once */
116 s.t. fb{i in 1..m, t in 1..kr[i]-1, j in er[i,t]..lr[i,t]}:
117 y[i,t,j] <= sum{jp in j+sr[i,t]+1..lr[i,t+1]} y[i,t+1,jp];
118 /* row i's (t+1)-th cluster must be placed to the right of its t-th
121 s.t. fc{j in 1..n, t in 1..kc[j]}:
122 sum{i in ec[j,t]..lc[j,t]} x[j,t,i] = 1;
123 /* column j's t-th cluster must appear in column j exactly once */
125 s.t. fd{j in 1..n, t in 1..kc[j]-1, i in ec[j,t]..lc[j,t]}:
126 x[j,t,i] <= sum{ip in i+sc[j,t]+1..lc[j,t+1]} x[j,t+1,ip];
127 /* column j's (t+1)-th cluster must be placed below its t-th cluster */
129 s.t. fe{i in 1..m, j in 1..n}:
130 z[i,j] <= sum{t in 1..kr[i], jp in er[i,t]..lr[i,t]:
131 j-sr[i,t]+1 <= jp and jp <= j} y[i,t,jp];
132 /* the double coverage constraint stating that if row i's j-th pixel
133 is painted black, then at least one of row i's clusters must be
134 placed in such a way that it covers row i's j-th pixel */
136 s.t. ff{i in 1..m, j in 1..n}:
137 z[i,j] <= sum{t in 1..kc[j], ip in ec[j,t]..lc[j,t]:
138 i-sc[j,t]+1 <= ip and ip <= i} x[j,t,ip];
139 /* the double coverage constraint making sure that if row i's j-th
140 pixel is painted black, then at least one of column j's clusters
143 s.t. fg{i in 1..m, j in 1..n, t in 1..kr[i], jp in er[i,t]..lr[i,t]:
144 j-sr[i,t]+1 <= jp and jp <= j}: z[i,j] >= y[i,t,jp];
145 /* the constraint to prevent white pixels from being covered by the
148 s.t. fh{i in 1..m, j in 1..n, t in 1..kc[j], ip in ec[j,t]..lc[j,t]:
149 i-sc[j,t]+1 <= ip and ip <= i}: z[i,j] >= x[j,t,ip];
150 /* the constraint to prevent white pixels from being covered by the
153 /* there is no need for an objective function here */
158 { printf{j in 1..n} " %s", if z[i,j] then "#" else ".";
164 /* These data correspond to the example above. */
170 param row : 1 2 3 4 :=
182 param col : 1 2 3 4 :=