这个题最好的方法是用bfs做, 用dfs很容易超时,所以需要很有效的剪枝。
题意:在n×m的地图上,0表示墙,1表示空地,2表示人,3表示目的地,4表示有定时炸弹重启器。定时炸弹的时间是6,人走一步所需要的时间是1。每次可以上、下、左、右移动一格。当人走到4时如果炸弹的时间不是0,可以重新设定炸弹的时间为6。如果人走到3而炸弹的时间不为0时,成功走出。求人从2走到3的最短时间。这个题中每个结点都是可以重复访问的,但其实,炸弹重置点不要重复走,因为,走到炸弹重置点时时间就会被设置为最大时间,当重新返回时时间又设成最大,但此时已走的步数肯定增加了,所以如果存在较优解的话那么肯定在第一次到这点后就可以找到较优解,这也是代码中剪枝的原理,只是将这种思想扩展到普通点而已,所以采用记忆化搜。
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 9;
int map[maxn][maxn],step[maxn][maxn],time[maxn][maxn];
int dir[4][2]={0,1,0,-1,1,0,-1,-0};
int n,m,sx,sy,dx,dy,minx;
void dfs(int x,int y,int len,int cnt)
{
if(x<0||x>=n||y<0||y>=m)<span style="white-space:pre"> </span>//这里各种边界,一定不要漏掉
return ;
if(len<=0||cnt>=minx)
return ;
if(map[x][y]==0)
return ;
if(map[x][y]==3)
{
if(cnt<=minx) minx=cnt;
return ;
}
if(map[x][y]==4)
len=6;
//下面的这个剪枝很重要,不剪就会超时
//从当前点x,y走到下一个可能点的距离大于从其他途径到tx,ty的距离,且到tx,ty点时的剩余时间大于由x,y点到tx,ty点后的剩余时间,就跳过
//这是因为结点可重复访问所以本身没标记,那么当上述条件满足时,由tx,ty开始的最优解已经求过(存在或者不存在),所以不需要再重复求了。
if(cnt>=step[x][y] && time[x][y]>=len) return;<span style="white-space:pre"> </span>//这里尝试画个图就很好理解了
step[x][y]=cnt;
time[x][y]=len;
int fx,fy,i;
for(i=0;i<4;i++)
{
fx=x+dir[i][0];
fy=y+dir[i][1];
dfs(fx,fy,len-1,cnt+1);
}
}
int main()
{
int t,i,j,len,cnt;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
for(j=0;j<m;j++)
{
time[i][j]=0;
step[i][j]=INT_MAX-3;
scanf("%d",&map[i][j]);//这里置一个大数,表示到i,j的步数无限大
if(map[i][j]==2)
{
sx=i;
sy=j;
}
if(map[i][j]==3)
{
dx=i;
dy=j;
}
}
len=6;
cnt=0;
minx=INT_MAX;
dfs(sx,sy,len,cnt);
if(minx==INT_MAX)
printf("-1\n");
else
printf("%d\n",minx);
}
return 0;
}