棋盘覆盖问题详解
1.问题描述:
方法一 分治法
- 首先回忆一下分治法的适用条件
- 1.问题规模缩小到一定程度后容易解决(当棋盘只有一个方格,则该方格必为特殊方格无需处理)。
- 2.问题能够被划分为若干个规模较小的相同子问题(我们考虑将大棋盘划分为四个大小相同的小棋盘,但是存在一个问题划分后只有一个小棋盘内含有特殊方格为和原问题相同的子问题,其余三个不含有特殊方格,不能使用分治方法解决。稍后解释~~~)。
- 3.子问题的解能够合并成为原问题的解(当小棋盘全部覆盖完毕,则原问题得到解决)。
- 4.不存在相同子问题(每次划分得到的小棋盘各不相同)。
解释2中的问题:
当k>0时,我们把规模为2k * 2k的大棋盘划分为2(k-1) * 2(k-1)的四个小棋盘。
则此时的特殊方格位于四个棋盘其中之一,为了将其余的三个棋盘也同样转化成和原问题相同的棋盘,我们只需要根据划分后特殊棋盘的位置选择合适的骨牌放置在大棋盘的汇合处。如图:
这样就得到了四个和原问题相同的规模更小的子问题,再将子问题进行相同的处理,直到问题规模变为1直接返回。
代码分析
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
const int N=1100;
int g[N][N]; //棋盘数组
int title=1; //骨牌编号
void SetColor(int fore = 7, int back = 0)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (back << 4) + fore);
}//背景颜色
void solve(int tr,int tc,int dr,int dc,int size){
/*tr棋盘左上角行号,tc棋盘左上角列号,size棋盘宽度,dr特殊位置行号,dc特殊位置列号*/
if(size==1) return; //递归出口
int t=title++;
int s=size/2; //分割棋盘
//处理左上角部分
if(dr<tr+s&&dc<tc+s){
solve(tr,tc,dr,dc,s);
}else{
g[tr+s-1][tc+s-1]=t;
solve(tr,tc,tr+s-1,tc+s-1,s);
}
//处理右上角部分
if(dr<tr+s&&dc>=tc+s){
solve(tr,tc+s,dr,dc,s);
}else{
g[tr+s-1][tc+s]=t;
solve(tr,tc+s,tr+s-1,tc+s,s);
}
//处理右下角部分
if(dr>=tr+s&&dc>=tc+s){
solve(tr+s,tc+s,dr,dc,s);
}else{
g[tr+s][tc+s]=t;
solve(tr+s,tc+s,tr+s,tc+s,s);
}
//处理左下角部分
if(dr>=tr+s&&dc<tc+s){
solve(tr+s,tc,dr,dc,s);
}else{
g[tr+s][tc+s-1]=t;
solve(tr+s,tc,tr+s,tc+s-1,s);
}
}
int main(){
int a,b,legth;
cin>>a>>b>>legth;
solve(1,1,a,b,legth);
for(int i=1;i<=legth;i++){
for(int j=1;j<=legth;j++){
if(g[i][j]){
SetColor(0, g[i][j]);
printf("%4d",g[i][j]);
}
else{
printf("%4d",g[i][j]);
}
}
printf("\n");
}
return 0;
}
代码思路:
输入棋盘左上角的坐标(此处为(1,1))和棋盘的宽度,然后借助solve函数进行处理,每次将原棋盘分割为4个小棋盘,然后根据左上、右上、右下、左下的顺时针顺序进行处理,如果特殊方格存在于该小棋盘则直接递归处理该小棋盘。否则,先将该小棋盘的指定位置覆盖上骨牌转换成和原问题相同的问题后在进行分割处理。
方法二 非递归化
借助队列实现
实现思路:
借助结构体存储棋盘相关信息(左上角的坐标,特殊方格的位置坐标,棋盘的宽度),先将原棋盘入队列,每次取出队首的元素,当队首元素的规模不为1时,对其进行处理,再将分割后的子棋盘入队,不断循环直至队列为空。
#include<bits/stdc++.h>
#include<windows.h>
void SetColor(int fore = 7, int back = 0)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (back << 4) + fore);
}//背景颜色
using namespace std;
const int N=1100;
int g[N][N];
int title=1;
struct Point{
int x;
int y;
int tx;
int ty;
int size;
}p;
queue<Point> q;
int main(){
int a,b,length;
cin>>a>>b>>length;
q.push({1,1,a,b,length});
while(!q.empty()){
Point c=q.front(); //取出队首元素
int tr,tc,dr,dc;
if(c.size!=1){
int s=c.size/2;
int t=title++;
tr=c.x;
tc=c.y;
dr=c.tx;
dc=c.ty;
if(dr<tr+s&&dc<tc+s){
q.push({tr,tc,dr,dc,s});
}else{
g[tr+s-1][tc+s-1]=t;
q.push({tr,tc,tr+s-1,tc+s-1,s});
}
if(dr<tr+s&&dc>=tc+s){
q.push({tr,tc+s,dr,dc,s});
}else{
g[tr+s-1][tc+s]=t;
q.push({tr,tc+s,tr+s-1,tc+s,s});
}
if(dr>=tr+s&&dc>=tc+s){
q.push({tr+s,tc+s,dr,dc,s});
}else{
g[tr+s][tc+s]=t;
q.push({tr+s,tc+s,tr+s,tc+s,s});
}
if(dr>=tr+s&&dc<tc+s){
q.push({tr+s,tc,dr,dc,s});
}else{
g[tr+s][tc+s-1]=t;
q.push({tr+s,tc,tr+s,tc+s-1,s});
}
q.pop();
}else{
//q.pop();
break;
}
}
for(int i=1;i<=length;i++){
for(int j=1;j<=length;j++){
if(g[i][j]){
SetColor(0, g[i][j]);
printf("%4d",g[i][j]);
}
else{
printf("%4d",g[i][j]);
}
}
printf("\n");
}
return 0;
}
注: 在处理时发现,只要此时队首元素的规模为1,则队列中其余元素的规模全为1都无需进行处理,因此当出现队首元素为1时可以直接终止循环,无需等到队列为空。
借助栈实现
实现思路:
借助结构体存储棋盘相关信息(左上角的坐标,特殊方格的位置坐标,棋盘的宽度),先将原棋盘入栈,每次取出栈顶的元素,当栈顶元素的规模不为1时,对其进行处理,再将分割后的子棋盘入栈,不断循环直至栈为空。
#include<bits/stdc++.h>
#include<windows.h>
using namespace std;
const int N=1100;
int g[N][N];
int title=1;
void SetColor(int fore = 7, int back = 0)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (back << 4) + fore);
}//背景颜色
struct Point{
int x;
int y;
int tx;
int ty;
int size;
}p;
stack<Point> st;
int main(){
int a,b,length;
cin>>a>>b>>length;
st.push({1,1,a,b,length});
while(!st.empty()){
Point c=st.top();
int tr,tc,dr,dc;
if(c.size!=1){
int s=c.size/2;
int t=title++;
tr=c.x;
tc=c.y;
dr=c.tx;
dc=c.ty;
st.pop();
if(dr<tr+s&&dc<tc+s){
st.push({tr,tc,dr,dc,s});
}else{
g[tr+s-1][tc+s-1]=t;
st.push({tr,tc,tr+s-1,tc+s-1,s});
}
if(dr<tr+s&&dc>=tc+s){
st.push({tr,tc+s,dr,dc,s});
}else{
g[tr+s-1][tc+s]=t;
st.push({tr,tc+s,tr+s-1,tc+s,s});
}
if(dr>=tr+s&&dc>=tc+s){
st.push({tr+s,tc+s,dr,dc,s});
}else{
g[tr+s][tc+s]=t;
st.push({tr+s,tc+s,tr+s,tc+s,s});
}
if(dr>=tr+s&&dc<tc+s){
st.push({tr+s,tc,dr,dc,s});
}else{
g[tr+s][tc+s-1]=t;
st.push({tr+s,tc,tr+s,tc+s-1,s});
}
}else{
st.pop();
//break;
}
}
for(int i=1;i<=length;i++){
for(int j=1;j<=length;j++){
if(g[i][j]){
SetColor(0, g[i][j]);
printf("%4d",g[i][j]);
}
else{
printf("%4d",g[i][j]);
}
}
printf("\n");
}
return 0;
}
附加
为了帮助大家清晰的理解棋盘覆盖,三种实现方法的具体步骤,笔者采用python编写了可视化的小工具。
棋盘覆盖工具.