[分治算法]棋盘覆盖

    在一个2^{k}\times 2^{k} 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
 

 

        当k = 1时,棋盘规模为2×2。此时,由数学归纳法可知:无论特殊方格在哪个位置(左上、左下、右上、右下),剩下的方格都可以用L型骨牌对其覆盖,如下图所示。

        因此,我们可以将一个2^{k}\times 2^{k}的棋盘经过多次划分直至k=1,也就是一个2×2的棋盘。下面展示一个例子:将 k=2 的棋盘(4×4)划分为 k=1的棋盘(2×2),如下图所示。大家可以发现:一个2^{k}\times 2^{k}的棋盘可以划分为 4 个 2^{k-1}\times 2^{k-1} 的棋盘。

         接下来,在划分之后的棋盘里,左上角子棋盘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)。

        第一次划分,将 2^{3}\times 2^{3} 的棋盘划分为4个 2^{2}\times 2^{2}的棋盘(红色直线)。

        按照代码 ,先判断 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号直线右下角子棋盘,需先设定特殊方格,再覆盖其他方格。(略)

最后整个棋盘的覆盖情况如下图所示:

        希望大家学有所获!

这是一个经典的计算机科学问题,被称为“棋盘覆盖问题”。可以使用分治算法来解决这个问题。具体来说,可以将棋盘分成四个大小相等的子棋盘,然后递归地解决每个子棋盘。在递归的过程中,需要注意特殊方格所在的位置,以便在覆盖子棋盘时不覆盖特殊方格。 以下是一种可能的解决方案的Python代码: ```python def cover(board, lab=1, top=0, left=0, side=None): if side is None: side = len(board) # Side length of subboard: s = side // 2 # Offsets for outer/inner squares of subboards: offsets = (0, -1), (side-1, 0) for dy_outer, dy_inner in offsets: for dx_outer, dx_inner in offsets: # If the outer corner is not set... if not board[top+dy_outer][left+dx_outer]: # ... label the inner corner: board[top+s+dy_inner][left+s+dx_inner] = lab # Next label: lab += 1 if s > 1: for dy in [0, s]: for dx in [0, s]: # Recursive calls, if s is at least 2: lab = cover(board, lab, top+dy, left+dx, s) # Return the next available label: return lab # Example usage: board = [[0]*8 for _ in range(8)] board[7][7] = -1 # Mark the special square cover(board) for row in board: print(row) ``` 这个代码使用了一个递归函数`cover()`,它接受一个棋盘、一个标签、一个左上角的坐标和一个子棋盘的边长。在每次调用中,它将子棋盘分成四个部分,并在每个部分的内角上打上标签。然后,如果子棋盘的边长至少为2,它将递归地调用自己来处理每个子棋盘。最后,它返回下一个可用的标签。 这个代码的输出应该是一个8x8的矩阵,其中特殊方格的值为-1,其他方格的值为1到4之间的整数,表示使用的不同形状的三格骨牌。这个输出可能不是唯一的,因为有多种不同的覆盖方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值