题目描述
在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋子,7颗黑色棋子,有两个空白地带,任何一颗黑白棋子都可以向上下左右四个方向移动到相邻的空格,这叫行棋一步.
黑白双方交替走棋,任意一方可以先走,如果某个时刻使得任意一种颜色的棋子形成四个一线(包括斜线),这样的状态为目标棋局。
输入输出格式
输入格式:
从文件中读入一个4*4的初始棋局,黑棋子用B表示,白棋子用W表示,空格地带用O表示。
输出格式:
用最少的步数移动到目标棋局的步数。
输入输出样例:
输入:
BWBO
WBWB
BWBW
WBWO
输出:
5
分析:
先讲讲我做这道题的悲惨经历,因为我太弱了想了半小时然后写一下卡一下的写出个BFS然后果不其然样例都没过,
然后换了种思路重写了一遍然后在机房3位dalao历经1小时的debug最后终于A了这道题,十分感谢这3位dalao.
接下来进入正题.
思路:
首先这是一道搜索加模拟题这道题是真的要我这个蒟蒻的命,先分析题目由得知我们有一个4*4的棋盘其中有两个字母为“O”的空白
然后在棋盘中其他字母为B(Black)或W(White),然后我们需要像走五子棋一样要么先走白要么先走黑当出现横排出现4格连续的“B”或“W或”竖列出现4格连续的“B”或“W”
亦或是斜着(包含左斜,右斜)出现4格连续的“B”或“W”只要出现其中一种情况就达成了目标情况(就像五子棋一样).
输出最小步数.
方法:
因为是最小步数我们首先考虑BFS显然这道题BFS是行的通的,但是有一点需要注意玩过五子棋都知道黑白谁先手结果可能不一样,因此我们需要跑两边BFS(1.白先手时2.黑先手时)
然后我们需要下一个棋盘的状况,我们可以用到和“八数码难题”一样的更新操作即我们可以先将每个矩阵压成一个字符串当需要判断是否成立目标状况时在变回矩阵判断,
压缩操作:
为什么要这样做,因为每次Map都在变而我们必须要存下每个不同的状况所以我们需要字符串存下每个不同状况.
void compression() { change.clear();//在压缩成一个字符串时当然需要将这个字符串清空 for(int i=1;i<=4;i++)//4*4的矩阵 for(int j=1;j<=4;j++) change.push_back(Map[i][j]); }
既然有压缩操作那就必须有拆分的操作
拆分操作:
为什么要这么做,由上面的压缩操作可以得到一个全新的棋盘状况但是我们需要判断当前的状况是否符合标准状况的条件但是很显然字符串是无法判断的
所以我们需要将这个字符串再拆分成一个全新的矩阵再用这个矩阵判断是否符合情况
void break_up(string a) { posx=1;posy=1; for(int i=0;i<a.length();i++) { if(posy==5) { posy=1; posx++; } Map[posx][posy]=a[i]; posy++; } }
上面都提到了判断操作
相信判断操作大家都会这里就不多做解释了
判断操作:
当符合情况就返回true否则就返回false
可以发现check里我们用的是string w 那就说明再判断前我们一定要将需要判断的字符串拆分成矩阵
bool check(string w) { for(int i=1;i<=4;i++)//横竖 { if(Map[i][1]==Map[i][2]&&Map[i][1]==Map[i][3]&&Map[i][1]==Map[i][4]) return true; if(Map[1][i]==Map[2][i]&&Map[1][i]==Map[3][i]&&Map[1][i]==Map[4][i]) return true; } if(Map[1][4]==Map[2][3]&&Map[1][4]==Map[3][2]&&Map[1][4]==Map[4][1]) return true;//右斜 if(Map[1][1]==Map[2][2]&&Map[1][1]==Map[3][3]&&Map[1][1]==Map[4][4]) return true;//左斜 return false; }
在贴整段代码之前我还要再BB一两句下面这些话可能对你理解这题的BFS有帮助
1.由题意我们可以知道这题的BFS其实就是将“O”这个点去更新整个棋盘的所有点,我用的是swap这个就是将两个点交换位置至于BFS怎么写看大家的习惯
但是要用swap的小伙伴要注意一下,当搜索不满足情况时因为我们已经交换这两个点所以我们必须再交换回去才能continue(即再swap一次)
2.有题意我们可以得知这题有两个空格“O”谁先走都没有规定因此我们必须写两个for循环这里也许需要思考一下为什么.
3.再有题意可得这是一个像五子棋一样的游戏所以我们必须一黑一白交替走棋(至于谁先我前面已经说了我们要注意这两种情况因为情况可能不同)
由此三点我们可以得出我们的结构体里需要存储的东西
struct Node { int x1,y1,x2,y2,t;//x1,y1为第一个空格“O”的位置 x2,y2为第二个“O”的位置 bool order;//借此判断走棋顺序 string s;//存储每一个矩阵压成的字符串 };
接下来我会贴出整段代码:
如果还有点懵的可以看看整段代码让你的思路更清晰
代码:
#include <iostream> #include <cstring> #include <cmath> #include <string> #include <queue> #include <map> using namespace std; const int INF=0x3f3f3f3f; char Map[5][5],tmp[5][5]; int startx1,starty1,startx2,starty2,posx,posy,ans=INF; map<string,bool> vis; string x,change; bool flag; int dirx[4]={0,1,-1,0}; int diry[4]={1,0,0,-1}; struct Node { int x1,y1,x2,y2,t;//这些变量名我在上面解释的很清楚 bool order; string s; }; void compression()//压缩操作 { change.clear(); for(int i=1;i<=4;i++) for(int j=1;j<=4;j++) change.push_back(Map[i][j]); } void break_up(string a)//拆分操作 { posx=1;posy=1; for(int i=0;i<a.length();i++) { if(posy==5) { posy=1; posx++; } Map[posx][posy]=a[i]; posy++; } } bool check(string w)//判断是否成立 { for(int i=1;i<=4;i++) { if(Map[i][1]==Map[i][2]&&Map[i][1]==Map[i][3]&&Map[i][1]==Map[i][4]) return true; if(Map[1][i]==Map[2][i]&&Map[1][i]==Map[3][i]&&Map[1][i]==Map[4][i]) return true; } if(Map[1][4]==Map[2][3]&&Map[1][4]==Map[3][2]&&Map[1][4]==Map[4][1]) return true; if(Map[1][1]==Map[2][2]&&Map[1][1]==Map[3][3]&&Map[1][1]==Map[4][4]) return true; return false; } void bfs(bool c) { vis[x]=true; queue<struct Node> que; while(!que.empty())//因为我们要进行第二次BFS所以有可能第一次的BFS的队列并没有弹空 {//所以进行第二次BFS时我们必须将队列弹空 que.pop(); } struct Node now; now.x1=startx1; now.y1=starty1; now.x2=startx2; now.y2=starty2; now.t=0; now.s=x; now.order=c; que.push(now); while(!que.empty()) { now=que.front(); break_up(now.s);//更新矩阵 /* 注意这个时候虽然我们的字符串更新了 但是我们的矩阵并未更新,如果不更新矩阵就判断是否成立的话那我们判断的就是上一个矩阵的状态这毫无疑问是错误的 */ if(check(now.s))//判断矩阵而不是字符串,就如上面说的一样 { ans=min(ans,now.t); return; } que.pop(); for(int i=0;i<4;i++) { int xx=now.x1+dirx[i]; int yy=now.y1+diry[i]; if(xx<1||xx>4||yy<1||yy>4) continue; swap(Map[now.x1][now.y1],Map[xx][yy]); if(now.order&&Map[now.x1][now.y1]=='B')//该走白子时走黑子的情况 这是不合法的 { swap(Map[xx][yy],Map[now.x1][now.y1]);//不符合情况时我们必须swap回到上一个状态 continue; } if(!now.order&&Map[now.x1][now.y1]=='W')//该走黑子时走白子的情况 这是不合法的 { swap(Map[xx][yy],Map[now.x1][now.y1]);//不符合情况时我们必须swap回到上一个状态 continue; } compression();//更新字符串(将更新的矩阵再压缩成一个新的字符串) if(vis[change])//判断这个字符串是否用过 { swap(Map[xx][yy],Map[now.x1][now.y1]);//我前面应该讲了swap当不符合情况时我们必须swap回到上一个状态 continue; } vis[change]=true; swap(Map[xx][yy],Map[now.x1][now.y1]);//当然需要swap回到上一个情况因为我们必须保证更新这个矩阵所有能行的情况时这个矩阵的Map不能改变 struct Node next; next.x1=xx;next.y1=yy;next.t=now.t+1;next.s=change;next.order=!now.order;next.x2=now.x2;next.y2=now.y2;//next.order=!now.order因为下次必须走和这次颜色不同的棋子 que.push(next); //next.x2=now.x2 next.y2=now.y2 因为这里是第一个空格的情况所以第二个空格的坐标并未改变 } for(int i=0;i<4;i++) { int xx=dirx[i]+now.x2; int yy=diry[i]+now.y2; if(xx<1||xx>4||yy<1||yy>4) continue; swap(Map[now.x2][now.y2],Map[xx][yy]); if(now.order&&Map[now.x2][now.y2]=='B')//该走白子时走黑子的情况 这是不合法的 { swap(Map[xx][yy],Map[now.x2][now.y2]);//不符合情况时我们必须swap回到上一个状态 continue; } if(!now.order&&Map[now.x2][now.y2]=='W')//该走黑子时走白子的情况 这是不合法的 { swap(Map[xx][yy],Map[now.x2][now.y2]);//不符合情况时我们必须swap回到上一个状态 continue; } compression();//更新字符串(将更新的矩阵再压缩成一个新的字符串) if(vis[change])//判断这个字符串是否用过 { swap(Map[xx][yy],Map[now.x2][now.y2]);//我前面应该讲了swap当不符合情况时我们必须swap回到上一个状态 continue; } vis[change]=true; swap(Map[xx][yy],Map[now.x2][now.y2]);//当然需要swap回到上一个情况因为我们必须保证更新这个矩阵所有能行的情况时这个矩阵的Map不能改变 struct Node next; next.x2=xx;next.y2=yy;next.t=now.t+1;next.order=!now.order;next.s=change;next.x1=now.x1;next.y1=now.y1;//next.order=!now.order因为下次必须走和这次颜色不同的棋子 que.push(next);//next.x1=now.x1 next.y1=now.y1 因为这里是第二个空格的情况所以第一个空格的坐标并未改变 } } return; } int main() { for(int i=1;i<=4;i++) for(int j=1;j<=4;j++) cin>>Map[i][j]; for(int i=1;i<=4;i++)//我们需要字符串x保存最原始的棋盘因为我们要跑两次BFS白棋先走和黑棋先走的两种情况 for(int j=1;j<=4;j++) x.push_back(Map[i][j]); for(int i=1;i<=4;i++)//找到两个空格的坐标 { for(int j=1;j<=4;j++) { if(Map[i][j]=='O'&&!flag) { startx1=i;starty1=j;//startx1 starty1 第一个空格 flag=true; } else if(Map[i][j]=='O'&&flag) { startx2=i;starty2=j;//startx2 starty2 第二个空格 } } } bfs(true);//true 是白棋先走的情况 bfs(false);//false 是黑棋先走的情况 if(ans==0)//这里为当我们输入的是符合情况 cout<<1;//我用的luogu的oj不知道为什么当输入的是符合情况时答案是1所以我们这里特判一下就可以了 else cout<<ans; return 0; }