在一个
个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
![]()
当k = 1时,棋盘规模为2×2。此时,由数学归纳法可知:无论特殊方格在哪个位置(左上、左下、右上、右下),剩下的方格都可以用L型骨牌对其覆盖,如下图所示。
因此,我们可以将一个的棋盘经过多次划分直至k=1,也就是一个2×2的棋盘。下面展示一个例子:将 k=2 的棋盘(4×4)划分为 k=1的棋盘(2×2),如下图所示。大家可以发现:一个
的棋盘可以划分为 4 个
的棋盘。
接下来,在划分之后的棋盘里,左上角子棋盘k=1并且含有特殊方格(绿色方格),所以一定能被L型骨牌覆盖。而右上角、左下角、右下角子棋盘均不含特殊方格,那么我们可以给它们分别设定特殊方格(粉色方格),使其能被L型骨牌覆盖。
但这里要注意,粉色方格并不是随意设定的,应该使其形成一个L型骨牌。所以对于每种类型的子棋盘都有固定的位置设定特殊方格。注意:含有特殊方格(绿色方格)的不能再设定特殊方格(粉色方格)。
- 对于左上角子棋盘,应该将其右下角设为特殊方格。
- 对于右上角子棋盘,应该将其左下角设为特殊方格。
- 对于左下角子棋盘,应该将其右上角设为特殊方格。
- 对于右下角子棋盘,应该将其左上角设为特殊方格。
下面给出代码
//tr表示棋盘左上角方格的行号 tc表示棋盘左上角方格的列号
//dr表示特殊方格的行号 dc表示特殊方格的列号
//size等于2的k次方。棋盘规格为2的k次方乘以2的k次方
void chessBoard(int tr, int tc, int dr, int dc, int size)
{
if (size == 1) //k=0
return;
int t = tile++, // L型骨牌号
//注意这里等价于t = tile 然后tile++ tile是全局变量 从0开始
s = size/2; // 分割棋盘
// 情形一:覆盖左上角子棋盘
if (dr < tr + s && dc < tc + s) // 特殊方格在此棋盘中
chessBoard(tr, tc, dr, dc, s);
else { // 此棋盘中无特殊方格
board[tr + s - 1][tc + s - 1] = t; // 用 t 号L型骨牌覆盖右下角
chessBoard(tr, tc, tr+s-1, tc+s-1, s);} // 覆盖其余方格
// 情形二:覆盖右上角子棋盘
if (dr < tr + s && dc >= tc + s) // 特殊方格在此棋盘中
chessBoard(tr, tc+s, dr, dc, s);
else { // 此棋盘中无特殊方格
board[tr + s - 1][tc + s] = t; // 用 t 号L型骨牌覆盖左下角
chessBoard(tr, tc+s, tr+s-1, tc+s, s);} // 覆盖其余方格
// 情形三:覆盖左下角子棋盘
if (dr >= tr + s && dc < tc + s) // 特殊方格在此棋盘中
chessBoard(tr+s, tc, dr, dc, s);
else {
board[tr + s][tc + s - 1] = t; // 用 t 号L型骨牌覆盖右上角
chessBoard(tr+s, tc, tr+s, tc+s-1, s);} // 覆盖其余方格
// 情形四:覆盖右下角子棋盘
if (dr >= tr + s && dc >= tc + s) // 特殊方格在此棋盘中
chessBoard(tr+s, tc+s, dr, dc, s);
else {
board[tr + s][tc + s] = t; // 用 t 号L型骨牌覆盖左上角
chessBoard(tr+s, tc+s, tr+s, tc+s, s);} // 覆盖其余方格
}
接下来演示一个较为复杂的例子,方便大家更好的理解。这里给出一个k=3的棋盘(8×8)。
第一次划分,将 的棋盘划分为4个
的棋盘(红色直线)。
按照代码 ,先判断 size == 8 != 1 (当前棋盘的边长),然后 t = 0,tile = 1(这里tile先赋值给t,再自增)。s = size/2 = 4(表示划分后每个子棋盘的边长)。接着开始分析四种情形:
1.对于1号直线左上角子棋盘,含有特殊方格,递归(继续对它进行划分,绿色直线),这是第二次划分。
此时,size == 4 != 1,然后 t = 1,tile = 2。s = size/2 = 2。接着开始分析四种情形:
1.1 对于2号直线左上角子棋盘,不含特定方格,需先设定特殊方格(设定在该子棋盘的右下角,下图中的粉色方格,此时 t = 1 ,故给其标号为1),再覆盖其他方格。
设定特殊方格后,根据代码可知,要继续覆盖其他方格,所以进行第三次划分(蓝色直线)。
1.1.1此时,size == 2 != 1,然后 t = 2,tile = 3。s = size/2 = 1。对于3号直线左上角、右上角、左下角子棋盘进入下一次递归会return,因为size == 1。所以此时给它们都标注2(黄色方格,此时 t = 2 ,故给其标号为2)。
返回上一层的递归。
1.2. 对于2号直线右上角子棋盘,不含特定方格,需先设定特殊方格。因为返回了上一层,所以 t=1,tile=3(全局变量)(下图中的粉色方格,此时 t = 1 ,故给其标号为1),再覆盖其他方格。
设定特殊方格后,根据代码可知,要继续覆盖其他方格,所以进行第四次划分(4号直线)。
1.2.1 此时,size == 2 != 1,然后 t = 3,tile = 4。s = size/2 = 1。对于4号直线左上角、右上角、右下角子棋盘进入下一次递归会return,因为size == 1。所以此时给它们都标注3(绿色方格,此时 t = 3 ,故给其标号为3)。
返回上一层的递归。
1.3.对于2号直线左下角子棋盘,含有特殊方格,递归(继续对它进行划分,深蓝色直线),这是第五次划分。
此时,size == 2 != 1,t = 4,tile = 5,s = size/2 = 1。对于5号线直线左上角、右上角、左下角子棋盘进入下一次递归会return,因为size == 1。所以此时给它们都标注4(绿色方格,此时 t = 4 ,故给其标号为4)。
对于为什么递归后 t = 4 而不是1这个问题,原因如下:当 t = 1 的时候,对于2号直线的左上角、右上角、右下角子棋盘都会使用当前的 t = 1,但唯独在2号直线左下角子棋盘的时候没有使用当前的 t = 1,而是递归之后的t。观察代码应能很快理解。
返回上一层的递归。
1.4对于2号直线右下角子棋盘,不含特定方格,需先设定特殊方格,再覆盖其他方格。因为返回了上一层,所以 t=1,tile=5(全局变量)(下图中的粉色方格,此时 t = 1 ,故给其标号为1),再覆盖其他方格。
设定特殊方格后,根据代码可知,要继续覆盖其他方格,所以进行第六次划分(6号直线)。
1.4.1 此时size == 2 != 1,然后 t = 5,tile = 6。s = size/2 = 1。对于6号直线右上角、左下角、右下角子棋盘进入下一次递归会return,因为size == 1。所以此时给它们都标注5(绿色方格,此时 t = 5 ,故给其标号为5)。
返回上一层递归。
- 对于1号直线右上角子棋盘,需先设定特殊方格,再覆盖其他方格。
这里跟大家解释一下为什么橙色方格填0。因为在最初的状态下 t = 0,因为进入了1号直线左上角子棋盘(含有特殊方格,所以进一步递归,没有用到 t = 0),只有当1号直线左上角子棋盘全部覆盖后,就会进入1号直线右上角子棋盘的覆盖,此时 t = 0。
后续步骤与1号直线左上角子棋盘的思路完全一致,就不展开说明了。
- 对于1号直线左下角子棋盘,需先设定特殊方格,再覆盖其他方格。(略)
- 对于1号直线右下角子棋盘,需先设定特殊方格,再覆盖其他方格。(略)
最后整个棋盘的覆盖情况如下图所示:
希望大家学有所获!