老题新解|棋盘覆盖
《信息学奥赛一本通》第162题:棋盘覆盖
题目描述
在一个 2k×2k2^k \times 2^k2k×2k 个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格。现在用 L 型(占 333 小格)骨牌覆盖棋盘上除了特殊方格的所有方格,各骨牌不能重叠。
现给出棋盘的大小和特殊方格所在的位置,请找出这种棋盘。
输入格式
输入两行。
第一行一个正整数 kkk。
第二行两个正整数 x,yx,yx,y,表示第 xxx 行第 yyy 列为特殊方格。
输出格式
输出一个 2k2^k2k 行 2k2^k2k 列的棋盘,每两个整数之间由空格隔开。特殊方格用 000 表示。对于其他方格,从 111 开始编号骨牌,输出该方格所属的骨牌编号。
输出任意一种符合要求的答案都算作正确。
输入输出样例 #1
输入 #1
2
2 2
输出 #1
2 2 3 3
2 0 1 3
4 1 1 5
4 4 5 5
说明/提示
本题共有 101010 个子任务,第 iii 个子任务中 k=ik=ik=i。每个子任务记 101010 分,你只有通过每个子任务中所有测试点才能获得该子任务的分数。
大家好,我是莫小特。
这篇文章给大家带来《信息学奥赛一本通》中的第 162 题:棋盘覆盖。
一、题目描述
洛谷的题号是:B2163 棋盘覆盖
二、题意分析
这道题是信息学奥赛一本通练习题的第 162 题。
根据输入格式描述,输入两行,第一行为正整数 k,第二行两个正整数 x 和 y,表示第 x 行第 y 列为特殊方格。
使用 int 类型输入数据。
int k;
int x,y;
cin>>k;
cin>>x>>y;
根据题目意思,k 表示输入的幂次,表示棋盘的大小 2k×2k2^k\times2^k2k×2k,所以需要定义一个二维数组来存储这个棋盘,使用 int 类型即可。
2k2^k2k 可以使用位运算实现,定义一个 int 类型的变量来存储。
int n=1<<k;//n=2^k
定义一个棋盘的二维数组,数组名为 board,全部初始值为 0,写在全局变量也可以,默认全部赋值为 0。
int board[1025][1025]={0};//全部赋值为0
接下来需要根据题目意思来完成输出,根据题目描述,要输出 2k2^k2k 行 2k2^k2k 列的棋盘,特殊方格用 0 表示,其他方格,从 1 开始编号,并且输出任意一种符合要求的答案都算正确,所以我们写一个函数来完成这个事情。
而根据题目的描述,要除去特殊的方格,所以我们令特殊的方格为 0,其实前面已经全部初始化为 0 了,保险起见,可以写一下。
board[x][y]=0;
接下来需要完成 L 型骨牌,骨牌的编号从 1 开始,所以定义一个变量存储编号。
int tile_id = 1;//编号从1开始
正式开始写棋盘的函数,因为只有赋值,所以无返回值类型,有5个参数:
(1)x, y: 当前子棋盘左上角,行和列。
(2)s:子棋盘的边长。
(3)r,c:当前子棋盘中的特殊格在全局坐标系中的位置。
void qp(int x,int y,int s,int r,int c)
{if(s==1) return; //大小为1的不放牌int id=tile_id++;//本次放置L型骨牌编号int half=s/2; //棋盘一半边长int midx=x+half-1; //中心四格的上半行int midy=y+half-1; //中心四格的左半列
}
需要判断特殊格在四个象限中的哪一个。
(1)左上 (top-left)
(2)右上 (top-right)
(3)左下 (bottom-left)
(4)右下 (bottom-right)
定义一个变量存储位置。
int quad;
if (r <= midx && c <= midy) quad = 1;
else if (r <= midx && c > midy) quad = 2;
else if (r > midx && c <= midy) quad = 3;
else quad = 4;
在中心放一个 L 型骨牌,覆盖除包含真实特殊格的象限外的三个中心格
中心四格坐标:
(midx, midy) (midx, midy+1)
(midx+1, midy) (midx+1, midy+1)
所以可写判断。
// 左上中心格
if (quad != 1) board[midx][midy] = id;
// 右上中心格
if (quad != 2) board[midx][midy + 1] = id;
// 左下中心格
if (quad != 3) board[midx + 1][midy] = id;
// 右下中心格
if (quad != 4) board[midx + 1][midy + 1] = id;
递归处理四个象限:
对于真实含有特殊格的象限,传入真实特殊格坐标;
对于其他象限,把我们刚放的对应中心格视为该象限的新“特殊格”。
// 左上
if (quad == 1) qp(x, y, half, r, c);
else qp(x, y, half, midx, midy);// 右上
if (quad == 2) qp(x, y + half, half, r, c);
else qp(x, y + half, half, midx, midy + 1);// 左下
if (quad == 3) qp(x + half, y, half, r, c);
else qp(x + half, y, half, midx + 1, midy);// 右下
if (quad == 4) qp(x + half, y + half, half, r, c);
else qp(x + half, y + half, half, midx + 1, midy + 1);
在主函数中执行。
qp(1, 1, n, x, y); // 从整个棋盘开始递归覆盖
最终输出即可,使用两重循环,下标从 1 开始到 n。
for(int i=1;i<=n;i++) {for (int j=1;j<=n;j++){if (j>1) cout<<' ';{cout << board[i][j];} }cout<<'\n';
}
按照样例输入对数据进行验证。
符合样例输出,到网站提交测评。
测试通过!
三、完整代码
该题的完整代码如下:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int board[1025][1025]={0};//全部赋值为0
int tile_id = 1;//编号从1开始
void qp(int x,int y,int s,int r,int c)
{if(s==1) return; //大小为1的不放牌int id=tile_id++;//本次放置L型骨牌编号int half=s/2; //棋盘一半边长int midx=x+half-1; //中心四格的上半行int midy=y+half-1; //中心四格的左半列int quad;if (r <= midx && c <= midy) quad = 1;else if (r <= midx && c > midy) quad = 2;else if (r > midx && c <= midy) quad = 3;else quad = 4;// 左上中心格if (quad != 1) board[midx][midy] = id; // 右上中心格 if (quad != 2) board[midx][midy + 1] = id; // 左下中心格 if (quad != 3) board[midx + 1][midy] = id; // 右下中心格 if (quad != 4) board[midx + 1][midy + 1] = id; // 左上if (quad == 1) qp(x, y, half, r, c);else qp(x, y, half, midx, midy);// 右上if (quad == 2) qp(x, y + half, half, r, c);else qp(x, y + half, half, midx, midy + 1);// 左下if (quad == 3) qp(x + half, y, half, r, c);else qp(x + half, y, half, midx + 1, midy);// 右下if (quad == 4) qp(x + half, y + half, half, r, c);else qp(x + half, y + half, half, midx + 1, midy + 1);
}
int main()
{int k;int x,y;cin>>k;cin>>x>>y;int n=1<<k;//n=2^kboard[x][y]=0;//// 从整个棋盘开始递归覆盖qp(1, 1, n, x, y); for(int i=1;i<=n;i++) {for (int j=1;j<=n;j++){if (j>1) cout<<' ';{cout << board[i][j];}}cout<<'\n';}return 0;
}
四、总结
本题考察了: 递归分治思想、二维数组坐标变换与边界处理。主要知识点是把大棋盘分成 4 个子棋盘,在中心放 L 形砖并把问题递归到四个子棋盘上。
常见易错点
(1)中心坐标(midx / midy)计算错误
正确写法(若子棋盘左上角为 (x,y)
,边长 s
):
int half = s/2;
int midx = x + half - 1;
int midy = y + half - 1;
错写成 x+half
/ y+half
会把中心四格错移一个格,覆盖出错。
(2)下标使用混淆(1-based / 0-based)
示例代码采用 1-based 下标(qp(1,1,n, x,y)
),数组 board
也按 1..n
存取。
若改为 0-based,则所有坐标 / mid 计算需相应调整。一定在代码中统一采用一种风格并保持一致。
(3)没有把“放入的中心格”作为子象限的特殊格
在放中心 L 形骨牌后,对不含真实特殊格的三个象限,必须把中心被放置的格子当作该子棋盘的特殊格传入递归,否则递归子问题不满足“每个子棋盘恰有一个特殊格”的前提。
(4)tile_id 编号管理错误
tile_id
应在每次放置 L 形骨牌时自增一次(并被四个中心格使用同一个 id)。若在递归中误用局部 id 或忘记自增,会导致不同骨牌重复编号或编号跳跃。
(5)递归终止条件错误
当 s == 1
时无需放牌(单格子就是特殊格或已被覆盖),必须 return
。若写成 s<=1
或在 s==2
时处理不当会导致重复或遗漏覆盖。常见正确处理:if (s==1) return;
(6)越界写入 / 未初始化数组
board
大小需至少 2^k+1
(若用 1-based),建议声明 board[1025][1025]
并初始化为 0。注意赋值 board[midx+1][midy+1]
时不要越界。
(7)输出格式与空格
要求每行 n
个数,用空格隔开且不要行尾多余空格。通常在内层循环:if (j>1) cout << ' '; cout << board[i][j];
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注我哦!
如果有更好的方法也可以在评论区评论哦,我都会看哒~
我们下集见~