Heyawake Part 2: Penalty Theory
by greenturtle3141, Dec 31, 2021, 9:24 AM
Reading Difficulty: 2-3/5
Prerequisites: Some graph theory would be nice. Read the previous post here if you do not know the rules of Heyawake.
Last time, we left off looking at this very challenging Heyawake puzzle:

https://puzz.link/p?heyawake/10/10/00000112244000000000003s00003s000000-1f2
I think it's safe to say that this is basically impossible to solve using basic logic alone, without a ton of guessing and/or trial and error. In this post, I will discuss a mathematical methodology for solving puzzles like the above that fundamentally changes the feel of this puzzle genre.
Credit: Heyawake Penalty Theory was originally discovered by "まり餡" (@agnomy on Twitter). The methodology was then generalized and propogated by Ivan Koswara.
If you don't care about the math and just want the method, scroll down to the section "Summary of the Penalty Method".
The Math
The key to these puzzles is that there are so many black squares that the number of ways to fit them all is extremely small (hopefully just one way, otherwise the puzzle has no unique solution, which is considered bad). This leads to the following natural questions:
The grand scheme will be to use the conditions to obtain three different bounds on some variables that relate to the total number of black squares. Figuring out what those variables should be is motivated by looking at...
The Connectedness Condition
The key to thinking about this condition mathematically is to draw a graph whose vertices are the white squares, such that there is an edge exactly when two vertices are orthogonally adjacent.
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
int[][] board = {{0,0,0,0},
{0,1,0,1},
{1,0,0,0},
{0,0,0,0}};
// Draw Grids
real hgap = 2;
drawGrid((0,0), 4, 4, 1);
drawGrid((hgap+4,0), 4, 4, 1);
// Left Shading
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 1){
shade((j,3-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < 4; ++i1){
for(int j1=0; j1 < 4; ++j1){
for(int i2=0; i2 < 4; ++i2){
for(int j2=0; j2 < 4; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(4+hgap) * ((j1+.5,3-i1+.5)--(j2+.5,3-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(4+hgap)*(j+.5,3-i+.5),r), rgb(.6,0,.4));
}
}
}
[/asy]](//latex.artofproblemsolving.com/c/3/d/c3dd053c4c66fadc157082ab7c2e9f8ef2bd7f3b.png)
As you can see, the connectedness of the white squares is equivalent to the connectedness of the graph. That is, the graph is in one piece. Let's call this graph
.
Adding in a black square is identical to deleting a vertex (and all of its edges) from
.
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
int[][] board = {{0,0,0,0},
{0,1,0,1},
{1,0,1,0},
{0,0,0,0}};
// Draw Grids
real hgap = 2;
drawGrid((0,0), 4, 4, 1);
drawGrid((hgap+4,0), 4, 4, 1);
// Left Shading
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 1){
shade((j,3-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < 4; ++i1){
for(int j1=0; j1 < 4; ++j1){
for(int i2=0; i2 < 4; ++i2){
for(int j2=0; j2 < 4; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(4+hgap) * ((j1+.5,3-i1+.5)--(j2+.5,3-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(4+hgap)*(j+.5,3-i+.5),r), rgb(.6,0,.4));
}
}
}
[/asy]](//latex.artofproblemsolving.com/7/3/6/7361a90b8cba3074b212a76e62777ece27f1c8d1.png)
Now it's disconnected. In theory, adding too many black squares (deleting too many vertices) would have to eventually disconnect the graph. We can capitalize on this intuition via the following theorem:
Theorem: Let
be a graph with
vertices and
edges. If
is connected then we must have
.
Proof
To use this, we first compute
, the number of edges. Suppose that the board is
. If there are no black squares, then
is an
square lattice. So, an easy argument
.
Now let's start adding in black squares. Shading a square black removes a vertex and its edges. How many edges? Well, that depends. Is it a corner square? A square on the edge? Or, is the square in the middle/center?
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide,pen c){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=c);
}
unitsize(1cm);
int[][] board = {{1,2,2,2,1},
{2,3,3,3,2},
{2,3,3,3,2},
{2,3,3,3,2},
{1,2,2,2,1}};
pen c1 = rgb(1,0,0);
pen c2 = rgb(.7,0,.3);
pen c3 = rgb(.4,0,.6);
pen[] colors = {c1,c2,c3};
for(int i=0;i<5;++i){
for(int j=0;j<5;++j){
shade((i,j),1,colors[board[i][j]-1]);
}
}
drawGrid((0,0), 5, 5, 1);
draw((4.5,4.5)--(6.5,3.5),linewidth(1.5));
draw((4.5,2.5)--(6.5,2.5),linewidth(1.5));
draw((3.5,1.5)--(6.5,1.5),linewidth(1.5));
label("Corner",(6.5,3.5),align=E,p=c1);
label("Edge",(6.5,2.5),align=E,p=c2);
label("Center",(6.5,1.5),align=E,p=c3);
[/asy]](//latex.artofproblemsolving.com/3/4/2/342bfad6a2d3414a2c3cd17eced392b39d450822.png)
The number of edges removed is different for each of these cases, and this is what motivates dividing the total number of black squares into a sum of three variables:
as the total number of black squares.
With this, we can now compute the number of edges. Observe that a corner black square will delete
edges, an edge square deletes
edges, and a center will delete
edges. It follows that:
A possible issue to keep in mind, however: Couldn't deleting a vertex delete less edges than what is listed above? For example, this "center vertex" deletes only
edges:
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
int[][] board = {{0,0,0,0},
{0,1,2,0},
{0,0,0,0},
{1,0,0,1}};
// Draw Grids
real hgap = 2;
fill(shift((2,2)) * ((0,0)--(1,0)--(1,1)--(0,1)--cycle),p=gray(0.5));
drawGrid((0,0), 4, 4, 1);
drawGrid((hgap+4,0), 4, 4, 1);
// Left Shading
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 1){
shade((j,3-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < 4; ++i1){
for(int j1=0; j1 < 4; ++j1){
for(int i2=0; i2 < 4; ++i2){
for(int j2=0; j2 < 4; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(4+hgap) * ((j1+.5,3-i1+.5)--(j2+.5,3-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(4+hgap)*(j+.5,3-i+.5),r), rgb(.6,0,.4));
}
}
}
draw((8.5,2.5)--(8.5,3.5),rgb(.6,0,.4)+dashed);
draw((8.5,2.5)--(8.5,1.5),rgb(.6,0,.4)+dashed);
draw((8.5,2.5)--(9.5,2.5),rgb(.6,0,.4)+dashed);
[/asy]](//latex.artofproblemsolving.com/e/c/4/ec4b3086768fdfc49fe319b3662e4ccf7b85a826.png)
Fortunately, this situation can never happen, because two black squares cannot be adjacent!
In sum, we may conclude that the number of edges in the graph must be
. Next, we need to compute the number of vertices. This is really easy: Every black square deletes exactly one vertex. Hence
. Plugging this into the theorem, we obtain the following bound:
This rearranges to:
This is the first bound we need. Note that this is not quite a bound on
, so we need to find some other inequalities.
Important Exercise: Prove or convince yourself that
is the most number of edges that can be deleted from
without disconnecting
. (Hint: The expression is
, now extend the Theorem.)
Bounds Along the Boundary
is the number of black squares along the boundary. What is the maximum value of
for an
board?
The trick is to subdivide the boundary squares into the following pieces:
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)),gray(0.5));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)),gray(0.5));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
pen wide = linewidth(2);
drawGrid((0,0), 8, 6, 1);
draw((0,0)--(0,6)--(8,6)--(8,0)--cycle,wide);
draw((1,1)--(1,5)--(7,5)--(7,1)--cycle,wide);
for(int i=1;i<=3;++i){
draw((2*i,0)--(2*i,1),wide);
draw((2*i,5)--(2*i,6),wide);
}
for(int j=1;j<=2;++j){
draw((0,2*j)--(1,2*j),wide);
draw((7,2*j)--(8,2*j),wide);
}
[/asy]](//latex.artofproblemsolving.com/d/0/d/d0d1f314385c9e42596e5dabe79b0c9592d77b0f.png)
Essentially, we put an L-tromino piece over each corner, and then fill the rest of the boundary with dominoes. Key Point: Each of these pieces can contain at most one black square! To count the number of these pieces:
There's a bit of a gap in this logic though: We can only tile the sides cleanly with dominoes if
and
are both even. If either are odd, then we'd need a lone
piece somewhere, so the pieces could look like this:
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)),gray(0.5));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)),gray(0.5));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
pen wide = linewidth(2);
drawGrid((0,0), 9, 7, 1);
draw((0,0)--(0,7)--(9,7)--(9,0)--cycle,wide);
draw((1,1)--(1,6)--(8,6)--(8,1)--cycle,wide);
for(int i=1;i<=3;++i){
draw((2*i,0)--(2*i,1),wide);
draw((2*i,6)--(2*i,7),wide);
}
for(int j=1;j<=2;++j){
draw((0,2*j)--(1,2*j),wide);
draw((8,2*j)--(9,2*j),wide);
}
draw((0,5)--(1,5),wide);
draw((8,5)--(9,5),wide);
draw((7,0)--(7,1),wide);
draw((7,6)--(7,7),wide);
[/asy]](//latex.artofproblemsolving.com/b/0/f/b0fba8ac648f2baf2772d2ddee2ada05fb2d2d4a.png)
It doesn't really matter where the
is. All that matters is that each region can have at most one black square. Now, to fix the above math to account for both the odd and even cases, we can just introduce the ceiling function. That is, there are
dominoes and
pieces. Altogether, we find the bound:

Exercise: Convince yourself that this bound is tight. That is, it is always possible to obtain
black squares on the boundary of the grid.
Now we have two inequalities, but it's not enough. We need one more inequality. The next inequality we get is absolutely ingenious. Buckle your seatbelts and steel yourself, because the logic is absolutely wild. Prepare for the greatest piece of mathematics you have ever seen.
The Final Inequality
because there are four corners.
Putting it All Together
We have the inequalities:


We need a bound on
. Fortunately this is really easy: Just add all the inequalities! This gives:
The RHS might not be divisible by
, so we'll leave it as this. Our first goal is essentially done! We've found some sort of bound for
.
Now, for the second goal: Define
to be the score of a configuration of black squares, and define
to be the upper bound we got. How can we use the "closeness" of the score to the "theoretically maximum" score
to deduce properties of the configuration?
Penalty Theory
Suppose the score was equal to the theoretical maximum, i.e.
. What would that mean? Looking back at the three inequalities we got earlier, that would have to imply that each of those inequalities were, in turn, equalities. This means that:
If instead the score was strictly less than the theoretical maximum
, then certain factors, called penalties, must be causing this. Penalties decrease the counts
,
, and
, widening the gaps between these values and their corresponding theoretical maximums. Evidently, the sum of these gaps must be
.
More precisely, these gaps are given by the following counts of penalties:
Some notes on how to spot/count these easily:
Example![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(.5cm);
int[][] board = {{0,0,0,1,0,0,0,0,1},
{0,1,0,0,1,0,1,0,0},
{1,0,1,0,0,0,0,0,1},
{0,0,0,0,1,0,1,0,0},
{1,0,1,0,0,1,0,0,1},
{0,1,0,0,1,0,1,0,0},
{0,0,1,0,0,0,0,0,0},
{1,0,0,0,1,0,1,0,1}};
// Draw Grids
real hgap = 2;
int m = 9;
int n = 8;
drawGrid((0,0), m, n, 1);
drawGrid((hgap+m,0), m, n, 1);
// Left Shading
for(int i=0; i < n; ++i){
for(int j=0; j < m; ++j){
if(board[i][j] == 1){
shade((j,n-1-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < n; ++i1){
for(int j1=0; j1 < m; ++j1){
for(int i2=0; i2 < n; ++i2){
for(int j2=0; j2 < m; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(m+hgap) * ((j1+.5,n-1-i1+.5)--(j2+.5,n-1-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < n; ++i){
for(int j=0; j < m; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(m+hgap)*(j+.5,n-1-i+.5),r), rgb(.6,0,.4));
}
}
}
// Pieces
pen piecePen = rgb(1,0,.5)+linewidth(2);
draw((0,0)--(0,n)--(m,n)--(m,0)--cycle,p=piecePen);
draw((1,1)--(1,n-1)--(m-1,n-1)--(m-1,1)--cycle,p=piecePen);
draw((2,0)--(2,1),p=piecePen);
draw((4,0)--(4,1),p=piecePen);
draw((6,0)--(6,1),p=piecePen);
draw((7,0)--(7,1),p=piecePen);
draw((8,2)--(9,2),p=piecePen);
draw((8,4)--(9,4),p=piecePen);
draw((8,6)--(9,6),p=piecePen);
draw((7,7)--(7,8),p=piecePen);
draw((5,7)--(5,8),p=piecePen);
draw((3,7)--(3,8),p=piecePen);
draw((2,7)--(2,8),p=piecePen);
draw((0,6)--(1,6),p=piecePen);
draw((0,4)--(1,4),p=piecePen);
draw((0,2)--(1,2),p=piecePen);
[/asy]](//latex.artofproblemsolving.com/9/1/b/91b7e7e5b6874d19a69819d34671e0bac77fea26.png)
(Higher Resolution Version)
In the configuration of black squares above, verify the following:
In theory, the sum of these numbers should be the difference between
and
. Let's verify that: There are
black squares giving
, and
. Indeed, it holds that
.
To reiterate, the importance of these quantities is that they must satisfy:
In certain specially-constructed "penalty Heyawakes", the RHS would, ideally, be a low number. If the LHS is low, this can set some major restrictions in how the black squares can be arranged, especially if you can spot some obvious instances of penalties right off the bat.
Summary of the Penalty Method
Example Solve
Let us solve the following Heyawake: https://puzz.link/p?heyawake/6/6/00c011021020b10

Step 1
so the score is
. Next,
. It follows that the total penalty is
.
Step 2
Step 3
As stated before, we must shade the other three corners black to prevent creation of any more penalty.
Step 4
The bottom three white squares must contain two black squares as such, otherwise it would not be maximally filled (and this will create penalty that we cannot spare).
Step 5
These two black squares near the center must be shaded, otherwise there will be a
loop of white cells.
Step 6
We may shade in a black square on the left edge in order to ensure that it is maximally filled. We may shade in a black square on the right edge to prevent the creation of a loop of white cells.
Step 7
The rest is easy.
Your Turn!
...and that's all I have for you in terms of Heyawake penalty theory! I hope this was fun to read. It's a remarkable application of math!
Have a merry new year.
Prerequisites: Some graph theory would be nice. Read the previous post here if you do not know the rules of Heyawake.
Last time, we left off looking at this very challenging Heyawake puzzle:

https://puzz.link/p?heyawake/10/10/00000112244000000000003s00003s000000-1f2
I think it's safe to say that this is basically impossible to solve using basic logic alone, without a ton of guessing and/or trial and error. In this post, I will discuss a mathematical methodology for solving puzzles like the above that fundamentally changes the feel of this puzzle genre.
Credit: Heyawake Penalty Theory was originally discovered by "まり餡" (@agnomy on Twitter). The methodology was then generalized and propogated by Ivan Koswara.
If you don't care about the math and just want the method, scroll down to the section "Summary of the Penalty Method".
The Math
The key to these puzzles is that there are so many black squares that the number of ways to fit them all is extremely small (hopefully just one way, otherwise the puzzle has no unique solution, which is considered bad). This leads to the following natural questions:
- What is the maximum number of black squares you can fit in a region, such that no two are adjacent and the white squares are connected? (We do not consider the essential rule and/or the interaction between multiple regions/rooms, it's hard to reason about them mathematically.)
- By examining the equality case in the maximum bound obtained, what restrictions does this imply on the configuration of black squares, if the number of black squares were indeed maximal?
The grand scheme will be to use the conditions to obtain three different bounds on some variables that relate to the total number of black squares. Figuring out what those variables should be is motivated by looking at...
The Connectedness Condition
The key to thinking about this condition mathematically is to draw a graph whose vertices are the white squares, such that there is an edge exactly when two vertices are orthogonally adjacent.
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
int[][] board = {{0,0,0,0},
{0,1,0,1},
{1,0,0,0},
{0,0,0,0}};
// Draw Grids
real hgap = 2;
drawGrid((0,0), 4, 4, 1);
drawGrid((hgap+4,0), 4, 4, 1);
// Left Shading
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 1){
shade((j,3-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < 4; ++i1){
for(int j1=0; j1 < 4; ++j1){
for(int i2=0; i2 < 4; ++i2){
for(int j2=0; j2 < 4; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(4+hgap) * ((j1+.5,3-i1+.5)--(j2+.5,3-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(4+hgap)*(j+.5,3-i+.5),r), rgb(.6,0,.4));
}
}
}
[/asy]](http://latex.artofproblemsolving.com/c/3/d/c3dd053c4c66fadc157082ab7c2e9f8ef2bd7f3b.png)
As you can see, the connectedness of the white squares is equivalent to the connectedness of the graph. That is, the graph is in one piece. Let's call this graph

Adding in a black square is identical to deleting a vertex (and all of its edges) from

![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
int[][] board = {{0,0,0,0},
{0,1,0,1},
{1,0,1,0},
{0,0,0,0}};
// Draw Grids
real hgap = 2;
drawGrid((0,0), 4, 4, 1);
drawGrid((hgap+4,0), 4, 4, 1);
// Left Shading
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 1){
shade((j,3-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < 4; ++i1){
for(int j1=0; j1 < 4; ++j1){
for(int i2=0; i2 < 4; ++i2){
for(int j2=0; j2 < 4; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(4+hgap) * ((j1+.5,3-i1+.5)--(j2+.5,3-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(4+hgap)*(j+.5,3-i+.5),r), rgb(.6,0,.4));
}
}
}
[/asy]](http://latex.artofproblemsolving.com/7/3/6/7361a90b8cba3074b212a76e62777ece27f1c8d1.png)
Now it's disconnected. In theory, adding too many black squares (deleting too many vertices) would have to eventually disconnect the graph. We can capitalize on this intuition via the following theorem:
Theorem: Let





Proof
The contrapositive is that if
, then
must be disconnected. To reason this: If we start with
vertices, then there are
"connected components" of the graph. Every edge can connect two components, decreasing the number of connected components by
. So we'd need at least
edges to decrease the number of connected components from
to
.








To use this, we first compute




The vertical edges can be viewed as a grid of dimensions
, and similarly for the horizontal edges.
will give the starting number of edges as 

Now let's start adding in black squares. Shading a square black removes a vertex and its edges. How many edges? Well, that depends. Is it a corner square? A square on the edge? Or, is the square in the middle/center?
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide,pen c){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=c);
}
unitsize(1cm);
int[][] board = {{1,2,2,2,1},
{2,3,3,3,2},
{2,3,3,3,2},
{2,3,3,3,2},
{1,2,2,2,1}};
pen c1 = rgb(1,0,0);
pen c2 = rgb(.7,0,.3);
pen c3 = rgb(.4,0,.6);
pen[] colors = {c1,c2,c3};
for(int i=0;i<5;++i){
for(int j=0;j<5;++j){
shade((i,j),1,colors[board[i][j]-1]);
}
}
drawGrid((0,0), 5, 5, 1);
draw((4.5,4.5)--(6.5,3.5),linewidth(1.5));
draw((4.5,2.5)--(6.5,2.5),linewidth(1.5));
draw((3.5,1.5)--(6.5,1.5),linewidth(1.5));
label("Corner",(6.5,3.5),align=E,p=c1);
label("Edge",(6.5,2.5),align=E,p=c2);
label("Center",(6.5,1.5),align=E,p=c3);
[/asy]](http://latex.artofproblemsolving.com/3/4/2/342bfad6a2d3414a2c3cd17eced392b39d450822.png)
The number of edges removed is different for each of these cases, and this is what motivates dividing the total number of black squares into a sum of three variables:

With this, we can now compute the number of edges. Observe that a corner black square will delete





![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
int[][] board = {{0,0,0,0},
{0,1,2,0},
{0,0,0,0},
{1,0,0,1}};
// Draw Grids
real hgap = 2;
fill(shift((2,2)) * ((0,0)--(1,0)--(1,1)--(0,1)--cycle),p=gray(0.5));
drawGrid((0,0), 4, 4, 1);
drawGrid((hgap+4,0), 4, 4, 1);
// Left Shading
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 1){
shade((j,3-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < 4; ++i1){
for(int j1=0; j1 < 4; ++j1){
for(int i2=0; i2 < 4; ++i2){
for(int j2=0; j2 < 4; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(4+hgap) * ((j1+.5,3-i1+.5)--(j2+.5,3-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < 4; ++i){
for(int j=0; j < 4; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(4+hgap)*(j+.5,3-i+.5),r), rgb(.6,0,.4));
}
}
}
draw((8.5,2.5)--(8.5,3.5),rgb(.6,0,.4)+dashed);
draw((8.5,2.5)--(8.5,1.5),rgb(.6,0,.4)+dashed);
draw((8.5,2.5)--(9.5,2.5),rgb(.6,0,.4)+dashed);
[/asy]](http://latex.artofproblemsolving.com/e/c/4/ec4b3086768fdfc49fe319b3662e4ccf7b85a826.png)
Fortunately, this situation can never happen, because two black squares cannot be adjacent!
In sum, we may conclude that the number of edges in the graph must be





Important Exercise: Prove or convince yourself that
![$(m-1)(n-1) - [X+2Y+3Z]$](http://latex.artofproblemsolving.com/f/d/0/fd066d104ff209cdae8d9fb1c9105810d0192a5b.png)



Bounds Along the Boundary



The trick is to subdivide the boundary squares into the following pieces:
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)),gray(0.5));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)),gray(0.5));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
pen wide = linewidth(2);
drawGrid((0,0), 8, 6, 1);
draw((0,0)--(0,6)--(8,6)--(8,0)--cycle,wide);
draw((1,1)--(1,5)--(7,5)--(7,1)--cycle,wide);
for(int i=1;i<=3;++i){
draw((2*i,0)--(2*i,1),wide);
draw((2*i,5)--(2*i,6),wide);
}
for(int j=1;j<=2;++j){
draw((0,2*j)--(1,2*j),wide);
draw((7,2*j)--(8,2*j),wide);
}
[/asy]](http://latex.artofproblemsolving.com/d/0/d/d0d1f314385c9e42596e5dabe79b0c9592d77b0f.png)
Essentially, we put an L-tromino piece over each corner, and then fill the rest of the boundary with dominoes. Key Point: Each of these pieces can contain at most one black square! To count the number of these pieces:
- There are
L-trominos
- Along the bottom edge, the tetronimoes take up
squares, so the rest of the
squares are taken up by dominoes. Hence
dominoes on the bottom edge. Doing the same for the other three edges, we get
dominoes in total.
- In total, this is
pieces, so
.
There's a bit of a gap in this logic though: We can only tile the sides cleanly with dominoes if



![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)),gray(0.5));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)),gray(0.5));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(1cm);
pen wide = linewidth(2);
drawGrid((0,0), 9, 7, 1);
draw((0,0)--(0,7)--(9,7)--(9,0)--cycle,wide);
draw((1,1)--(1,6)--(8,6)--(8,1)--cycle,wide);
for(int i=1;i<=3;++i){
draw((2*i,0)--(2*i,1),wide);
draw((2*i,6)--(2*i,7),wide);
}
for(int j=1;j<=2;++j){
draw((0,2*j)--(1,2*j),wide);
draw((8,2*j)--(9,2*j),wide);
}
draw((0,5)--(1,5),wide);
draw((8,5)--(9,5),wide);
draw((7,0)--(7,1),wide);
draw((7,6)--(7,7),wide);
[/asy]](http://latex.artofproblemsolving.com/b/0/f/b0fba8ac648f2baf2772d2ddee2ada05fb2d2d4a.png)
It doesn't really matter where the




Exercise: Convince yourself that this bound is tight. That is, it is always possible to obtain

Now we have two inequalities, but it's not enough. We need one more inequality. The next inequality we get is absolutely ingenious. Buckle your seatbelts and steel yourself, because the logic is absolutely wild. Prepare for the greatest piece of mathematics you have ever seen.
The Final Inequality

Putting it All Together
We have the inequalities:







Now, for the second goal: Define



Penalty Theory
Suppose the score was equal to the theoretical maximum, i.e.

- We deleted the maximum possible number of edges from the white-square graph
.
- Every piece along the boundary of the grid has a black square.
- Every corner square is black.
If instead the score was strictly less than the theoretical maximum





More precisely, these gaps are given by the following counts of penalties:
, which (by an Exercise) is the most number of edges we may delete from the white-square adjacency graph
without disconnecting it.
, which (quite directly) is the deviation from the maximum of the number of black squares on the boundary.
, which is the number of corner squares that aren't black.
Some notes on how to spot/count these easily:
- If
, that means that there can't be any loops that can be formed by traversing white squares. For instance, there cannot exist a
pool of white squares. More generally,
can be determined by counting the number of such white-square loops, with the caveat that you cannot count a loop that is formed by the loop segments of previously counted loops. For example, a
pool of white squares will form only
loops of white squares, not
.
can be computed by first dividing the boundary into pieces in the way we did before. Then, count the number of pieces that are empty.
can be counted by using your fingers.
Example
![[asy]
void drawGrid(pair bl, int width, int height, real sqSide){
for(int i = 0; i <= width; ++i){
draw((bl+(i*sqSide,0))--(bl+(i*sqSide,height*sqSide)));
}
for(int j = 0; j <= height; ++j){
draw((bl+(0,j*sqSide))--(bl+(width*sqSide,j*sqSide)));
}
}
void shade(pair bl, real sqSide){
fill(shift(bl) * ((0,0)--(sqSide,0)--(sqSide,sqSide)--(0,sqSide)--cycle),p=gray(0.1));
}
unitsize(.5cm);
int[][] board = {{0,0,0,1,0,0,0,0,1},
{0,1,0,0,1,0,1,0,0},
{1,0,1,0,0,0,0,0,1},
{0,0,0,0,1,0,1,0,0},
{1,0,1,0,0,1,0,0,1},
{0,1,0,0,1,0,1,0,0},
{0,0,1,0,0,0,0,0,0},
{1,0,0,0,1,0,1,0,1}};
// Draw Grids
real hgap = 2;
int m = 9;
int n = 8;
drawGrid((0,0), m, n, 1);
drawGrid((hgap+m,0), m, n, 1);
// Left Shading
for(int i=0; i < n; ++i){
for(int j=0; j < m; ++j){
if(board[i][j] == 1){
shade((j,n-1-i),1);
}
}
}
// Right Graph
for(int i1=0; i1 < n; ++i1){
for(int j1=0; j1 < m; ++j1){
for(int i2=0; i2 < n; ++i2){
for(int j2=0; j2 < m; ++j2){
if( (i1==i2 && abs(j1-j2)==1) || (j1==j2 && abs(i1-i2)==1) ){
if(board[i1][j1]==0 && board[i2][j2]==0){
draw(shift(m+hgap) * ((j1+.5,n-1-i1+.5)--(j2+.5,n-1-i2+.5)),p=linewidth(1.3)+rgb(.6,0,.4));
}
}
}
}
}
}
real r = 0.1;
for(int i=0; i < n; ++i){
for(int j=0; j < m; ++j){
if(board[i][j] == 0){
filldraw(circle(shift(m+hgap)*(j+.5,n-1-i+.5),r), rgb(.6,0,.4));
}
}
}
// Pieces
pen piecePen = rgb(1,0,.5)+linewidth(2);
draw((0,0)--(0,n)--(m,n)--(m,0)--cycle,p=piecePen);
draw((1,1)--(1,n-1)--(m-1,n-1)--(m-1,1)--cycle,p=piecePen);
draw((2,0)--(2,1),p=piecePen);
draw((4,0)--(4,1),p=piecePen);
draw((6,0)--(6,1),p=piecePen);
draw((7,0)--(7,1),p=piecePen);
draw((8,2)--(9,2),p=piecePen);
draw((8,4)--(9,4),p=piecePen);
draw((8,6)--(9,6),p=piecePen);
draw((7,7)--(7,8),p=piecePen);
draw((5,7)--(5,8),p=piecePen);
draw((3,7)--(3,8),p=piecePen);
draw((2,7)--(2,8),p=piecePen);
draw((0,6)--(1,6),p=piecePen);
draw((0,4)--(1,4),p=piecePen);
draw((0,2)--(1,2),p=piecePen);
[/asy]](http://latex.artofproblemsolving.com/9/1/b/91b7e7e5b6874d19a69819d34671e0bac77fea26.png)
(Higher Resolution Version)
In the configuration of black squares above, verify the following:
(Use the pink outlines to help count the number of empty pieces)
(Use the graph on the right to help count the number of loops)
In theory, the sum of these numbers should be the difference between






To reiterate, the importance of these quantities is that they must satisfy:

Summary of the Penalty Method
- When a Heyawake puzzle seems difficult, and the total number of squares seems more or less known and near-maximal, consider Penalty Theory.
- Count the number of black squares and triple it. This is the score of the puzzle.
- If the board is
, compute
. Then, subtract the score obtained from the previous step. The resulting number is the total penalty,
.
must be equal to the sum of the following three quantities:
, the number of corner squares that are not shaded black.
, how far away the number of black squares along the boundary (including corners) is from the theoretical maximum.
, the number of loops you can form by traversing white squares. In counting this, do not count loops that can be formed by segments from previously counted loops. More Formally
is the maximum number of edges that can be removed from the white-square adjacency graph without disconnecting it.
- Look for easy-to-spot instances of penalties. If you can find
penalties, then you have found all penalties, meaning that the rest of the grid cannot have any penalties (this is a TON of information!).
Example Solve
Let us solve the following Heyawake: https://puzz.link/p?heyawake/6/6/00c011021020b10

Step 1




Step 2
Since the total penalty is exactly
, we must have
. Observe that the bottom-right corner cannot be shaded black. Thus,
and we have found all of the total penalty. Thus, there are no more penalties. In continuing, we may assume that the other three corners are black, that the border is maximally filled, and that there are absolutely no loops of white squares.



Step 3

As stated before, we must shade the other three corners black to prevent creation of any more penalty.
Step 4

The bottom three white squares must contain two black squares as such, otherwise it would not be maximally filled (and this will create penalty that we cannot spare).
Step 5

These two black squares near the center must be shaded, otherwise there will be a

Step 6

We may shade in a black square on the left edge in order to ensure that it is maximally filled. We may shade in a black square on the right edge to prevent the creation of a loop of white cells.
Step 7

The rest is easy.
Your Turn!
- Solve my "Floating orb": https://puzz.link/p?heyawake/6/6/000c0i0010gsb10
HintAs before, there is exactly one penalty. The penalty should be caused by theregion.
- Solve my "P-Pentomino": https://puzz.link/p?heyawake/6/6/00k4000643g0c0
HintThe pentomino creates a loop. Also, don't forget standard Heyawake rules! - Solve my extremely sus puzzle: https://puzz.link/p?heyawake/14/12/000000000000c450pnj72cop105k0000000000000041u71oa02g070e10803c0-310051
Hintand
so the total penalty is
. The existing shapes should reveal all penalty locations off the bat.
- Solve this puzzle by @agnomy: https://puzz.link/p?heyawake/10/10/04080g1020490i14280000000000vv000000f5526
HintTotal penalty is. Think about which room is likely to contain penalties. You won't know exactly where the penalties are, but identifying the general location is a huge step.
- Solve this puzzle by Ivan Koswara: https://puzz.link/p?heyawake/8/7/060o00000081g2000000-121g
HintTotal penalty is. Observe that if the top-left corner were not shaded black, then this would contribute both a
penalty and a
penalty.
- Solve another one by @agnomy:
https://puzz.link/p?heyawake/12/12/00000o0003063cc0o00030000000008020080a4a92a02008020000-2811111111 - Solve this puzzle by Plurmorant:
https://puzz.link/p?heyawake/10/10/00000112244000000000003s00003s000000-1f2
HintTotal penalty is. This should let you completely solve the
room. To continue, look carefully at how the right edge can be filled in order to prevent creation of any more penalty.
- Solve my variant Heyawake, in which the board is a rotated diamond shape rather than a rectangle. Ignore the outer region when solving. Answer check will not work, you should verify correctness manually. https://puzz.link/p?heyawake/v:/23/23/0000006000280011000g40080g040202508118p0g0048000k0k02g5012180g8a0812g4040200g10020g00880014000600000000080002g000h0004200104008080260g0g09043121000481g0900012000840020800g0hg401010020800420008g000k0001000g-56h1Ok HintFirst, rederive the penalty theory for boards of this shape.
...and that's all I have for you in terms of Heyawake penalty theory! I hope this was fun to read. It's a remarkable application of math!
Have a merry new year.
This post has been edited 14 times. Last edited by greenturtle3141, Jan 1, 2022, 4:21 AM