1043.跳马
时限:1000ms 内存限制:10000K 总时限:3000ms
描述
在国际象棋中,马的走法与中车象棋类似,即俗话说的“马走日”,下图所示即国际象棋中马(K)在一步能到达的格子(其中黑色的格子是能到达的位置)。
现有一200*200大小的国际象棋棋盘,棋盘中仅有一个马,给定马的当前位置(S)和目标位置(T),求出马最少需要多少跳才能从当前位置到达目标位置。
输入
本题包含多个测例。输入数据的第一行有一个整数N(1<=N<=1000),表示测例的个数,接下来的每一行有四个以空格分隔的整数,分别表示马当前位置及目标位置的横、纵坐标C(x,y)和G(x,y)。坐标由1开始。
输出
对于每个测例,在单独的一行内输出一个整数,即马从当前位置跳到目标位置最少的跳数。
输入样例
2
1 1 2 1
1 5 5 1
输出样例
3
4
解析:这是一个典型的分支限界法用广搜和队列来解决的问题。一般来说,回溯法用到的是深搜,所以不断嵌套的调用函数;分支限界法用到的是广搜,没有嵌套调用的说法,只有用队列进行操作。我以前一直存在一个误区,为什么回溯即深搜结束后不能用return返回结果并结束输出,而广搜就可以。大家看两个函数的差别就可以,dfs嵌套调用,return只是返回上一层函数,不会结束整个深搜,深搜是遍历了整个子集树,最后有另一个循环找出最优的结果,因此是void dfs(),而广搜是找到结果就输出,不用便利整个子集树,因此是int bfs(),后者是有返回值的,返回值就是目标值,而广搜就一定要用到栈和队列(在这点上c++的方便性远高于c语言),深搜就一定会用到嵌套的递归函数。这是二者的本质差别。
下面来说一下这道题。跟前面的题一样,采用广搜的思路,从当前节点开始先一次性找出它所有的直属子节点,看有没有附和条件的,若没有就入队列并找它兄弟节点的全部子节点,直到所有兄弟节点全部找完没找到入队列才又从下层节点找。因为相同一层节点的步数是相同的,而子节点比父节点的步数大一步,广搜就是找到最小的符合要求的步数。当然,一个相同的坐标可以是不同层的节点(因为它有不同的方法抵达),所以可以有不同的步数,我们要找到最小的步数,就要找到最早到达它的方法。
所以这种题就要用到队列来操作。记录当前队列首元素,然后将其出队列,用记录的当前队列首位元素遍历所有的下层节点,遍历过程中若有符合要求的,直接return就可以,若没有就一一入队列准备以后使用,直到找到目标值为止。
#include <iostream>
#include<string.h>
#include<queue>
using namespace std;
struct addr{
queue<int>x;
queue<int>y;
}addr;
int sx,sy,tx,ty;
int step[201][201],used[201][201];
int dirx[8]={1,2,2,1,-1,-2,-2,-1},diry[8]={2,1,-1,-2,2,1,-1,-2};
void init()
{
memset(step,0,sizeof(step));
memset(used,0,sizeof(used));
used[sx][sy]=1;
step[sx][sy]=0;
addr.x.push(sx);
addr.y.push(sy);
}
void empty()
{
while(!addr.x.empty())
addr.x.pop();
while(!addr.y.empty())
addr.y.pop();
}
int bfs()
{
while(1)
{
int cx,cy;
cx=addr.x.front();
cy=addr.y.front();
addr.x.pop();
addr.y.pop();
for(int i=0;i<8;i++)
{
int vx,vy;
vx=cx+dirx[i];
vy=cy+diry[i];
if(vx==tx&&vy==ty)
return step[cx][cy]+1;
else
{
if(used[vx][vy]==0&&vx>0&&vx<=200&&vy>0&&vy<=200)
{
addr.x.push(vx);
addr.y.push(vy);
step[vx][vy]=step[cx][cy]+1;
used[vx][vy]=1;
}
}
}
}
}
int main()
{
int n;
cin>>n;
while(n--)
{
cin>>sx>>sy>>tx>>ty;
empty();
init();
int num=bfs();
cout<<num<<endl;
}
return 0;
}