本篇主要内容是叙述我思考棋盘覆盖问题的心路历程和最终解决。
今天花了1个多小时写棋盘覆盖问题,然后写出来的代码是一坨@_@
索性看了题解代码,结果发现还是看不懂,就去看了讲解视频。基本思路是理解了。
然后又花了2个多小时自己写思路和代码,终于运行通过了。
现在,我在此重新回顾总结一下棋盘覆盖问题给我带来的思考。
————————————————————
先把题目拿出来看看:
先说说我原来写的错误的且未完成的代码,如下:
#include <cstdio>
#include <cmath>
const int MAXNUM = 300;
bool a[MAXNUM][MAXNUM] = {false};
int corn[MAXNUM][2];
int outLine;
int idx = 0;
bool cover1(int i, int j){
if(a[i][j] == false && a[i][j+1] == false && a[i+1][j+1] == false){
return true;
}else{
return false;
}
}
void _cover1(){
a[i][j] = true;
a[i][j+1] = true;
a[i+1][j+1] = true;
corn[idx][0] = i;
corn[idx][1] = j+1;
}
bool cover2(int i, int j){
if(a[i][j] == false && a[i+1][j] == false && a[i+1][j-1] == false){
return true;
}else{
return false;
}
}
void _cover2(){
a[i][j] = true;
a[i+1][j] = true;
a[i+1][j-1] = true;
corn[idx][0] = i+1;
corn[idx][1] = j;
}
bool cover3(int i, int j){
if(a[i][j] == false && a[i][j+1] == false && a[i+1][j] == false){
a[i][j] = true;
a[i][j+1] = true;
a[i+1][j] = true;
return true;
}else{
return false;
}
}
void _cover3(){
a[i][j] = true;
a[i][j+1] = true;
a[i+1][j] = true;
corn[idx][0] = i;
corn[idx][1] = j;
}
bool cover4(int i, int j){
if(a[i][j] == false && a[i+1][j] == false && a[i+1][j+1] == false){
return true;
}else{
return false;
}
}
void _cover4(){
a[i][j] = true;
a[i+1][j] = true;
a[i+][j+1] = true;
corn[idx][0] = i+1;
corn[idx][1] = j;
}
bool comple = false;
int rowIdx = 1,colIdx = 1;
bool isComple(){
comple = true;
for(rowIdx = 1; rowIdx < outLine; rowIdx++){
for(colIdx = 1; colIdx < outLine; colIdx++){
if(!a[colIdx][colIdx]){
comple = false;
return false;
}
}
}
if(comple) return true;
}
void getSolution(){
//还不知道我要写啥
}
int main(){
int k,cx,cy;
scanf("%d%d%d",&k,&cx,&cy);
a[cx][cy] = true;
outLine = (int)pow(2.0,(double)k) + 1;
for(int i=0; i<= outLine; i++){
a[0][i] = true;
a[i][0] = true;
a[outLine][i] = true;
a[i][outLine] = true;
}
getSolution();
return 0;
}
我一开始的思路吧,是暴力解决,想把所以覆盖方法走一遍。把棋盘方格用一个二维数组表达出来,然后按x从小到大,y从下到大的顺序依次把方格用四种骨牌覆盖方法覆盖,再把已覆盖的方格的二维数组对应值设为true,但是我发现这种方式压根无法推进,也无法判断怎么覆盖一定有解。总之,行不通...
下面我们来思考如何使用分治思想解决这道题。分治,分治,即分而治之。先思考一下2^(k+1) * 2^(k+1)棋盘和2^k * 2^k棋盘有啥关系。没错,2^(k+1) * 2^(k+1)棋盘可以分割为四个2^k * 2^k棋盘,换言之,2^(k+1) * 2^(k+1)棋盘中有四个2^k * 2^k棋盘。如果要将该问题的规模缩小,也就是说要将该问题分解,那么我们将原规模的问题分解出规模更小的原问题。分解出规模更小的原问题过程如下:
由上面过程我们就得到了递归式和递归边界。那么,如何来书写代码呢?
首先,我们考虑输入和输出所需的数据结构类型。
通过上面的分析,可知,较优的方案是输入由三个int型变量存储,输出用一个结构体数组存储。
有了这些分析后,我们就知道了写代码的关键就是定义这些变量然后实现递归函数。
这里写递归函数的时候,我又犯了两个致命的错误,先不说,看看代码:
第一个致命的错误就是我一开始忽略了上图上的紫色部分代码,即递归边界。
第二个致命的错误就是我的递归函数和结构体数组中的坐标值写错了!!!(注意,坐标一定是相对坐标,其实所谓的绝对坐标也是相对坐标,只不过相对起始点为原点罢了。所以我们面对二维或三维空间确定坐标时一定要思考我们是相对哪个起始点或者说相对哪些坐标轴的坐标,然后再加上距离计算出相对坐标。这里我直接用距离来表示坐标了,而忽略了相对坐标起始点,就错了)
下面是正确运行的代码:
#include <cstdio>
#include <cmath> //pow
#include <algorithm> //sort
using namespace std;
const int MAXNUM = (256*256-1)/3;
int num = 0;
struct Point{
int x;
int y;
Point(){}
Point(int _x,int _y){
x = _x;
y = _y;
}
}card[MAXNUM];
bool cmp(Point a, Point b){
if(a.x != b.x){
return a.x < b.x;
}else{
return a.y < b.y;
}
}
void cover(int firx,int firy, int cx, int cy, int n){ //firx意为first-x,firy意为first-y,cx,cy为特殊方格的横纵坐标,n为棋盘边长
if(n == 1) return;
int half = n / 2;
if(cx <= firx-1+ half && cy <= firy-1+ half){
card[num++] = Point(firx-1+half+1,firy-1+half+1);
cover(firx,firy,cx,cy,half);
}else{
cover(firx,firy,firx-1+half,firy-1+half,half);
}//左下
if(cx > firx-1+ half && cy <= firy-1+ half){
card[num++] = Point(firx-1+half,firy-1+half+1);
cover(firx-1+half+1,firy,cx,cy,half);
}else{
cover(firx-1+half+1,firy,firx-1+half+1,firy-1+half,half);
}//右下
if(cx <= firx-1+ half && cy > firy-1+ half){
card[num++] = Point(firx-1+half+1,firy-1+half);
cover(firx,firy-1+half+1,cx,cy,half);
}else{
cover(firx,firy-1+half+1,firx-1+half,firy-1+half+1,half);
}//左上
if(cx > firx-1+ half && cy > firy-1+ half){
card[num++] = Point(firx-1+half,firy-1+half);
cover(firx-1+half+1,firy-1+half+1,cx,cy,half);
}else{
cover(firx-1+half+1,firy-1+half+1,firx-1+half+1,firy-1+half+1,half);
}//右上
}
int main(){
int k,cx,cy;
scanf("%d%d%d",&k,&cx,&cy);
int n = (int)pow(2.0,(double)k);
cover(1,1,cx,cy,n);
sort(card,card + num,cmp);
for(int i=0; i<num; i++){
printf("%d %d\n",card[i].x,card[i].y);
}
return 0;
}
最后总结一下解决该题的收获以结束此篇:
1.需要排序时避免使用二维数组,用二维数组实现可以改为用结构体数组实现,便于用sort函数排序。
2.分治思想解决问题的关键在于将原问题分解出规模减小了的原问题。
3.递归函数一定要具有两个要素:1.递归式 2.递归边界。(缺一不可)
4.坐标一定是相对坐标,确定坐标时候只需确定两个要素:1.相对点 2.和相对点之间的横纵距离