仙岛求药(二)
【试题描述】
咳,上回说到李逍遥去求药,其实他找到药之后,还需要给他的婶婶送过去,所以他需要在最短的时间内找到要并且到达婶婶('s')所在的位置。
【输入要求】
输入有多组测试数据. 每组测试数据以两个非零整数 M 和 N 开始,两者均不大于100。M 表示迷阵行数, N 表示迷阵列数。接下来有 M 行, 每行包含N个字符,不同字符分别代表不同含义:
1) ‘@’:少年李逍遥所在的位置;
2) ‘.’:可以安全通行的方格;
3) ‘#’:有怪物的方格;
4) ‘*’:仙药所在位置。
5) 's' : 婶婶所在的位置。
当在一行中读入的是两个零时,表示输入结束。
【输出要求】
对于每组测试数据,分别输出一行,该行包含李逍遥找到仙药并且送给婶婶需要穿过的最少的方格数目(计数包括初始位置的方块)。如果他不可能找到仙药, 则输出 -1。
【输入实例】
8 8 .@##...# #....#.# #.#.##.. *.#.###. #.#...#. ..###*#. ...#.s.. .#.*.### 0 0
【输出实例】
10
【其他说明】
无
【试题分析】
仔细一看,可以瞬间想到:遍历到每个仙药再回到婶婶,求最小值,那么只需要这么写:
#include<iostream> #include<cstring> using namespace std; char map[101][101]; int a[101][101],ans=0,m,n; void res(int u,int v,int i,int j) { int t=a[u][v]; if(u==i&&v==j) ans=t; t++; if(v<m-1&&map[u][v+1]!='#'&&a[u][v+1]>t) { a[u][v+1]=t; res(u,v+1,i,j); } if(u>0&&map[u-1][v]!='#'&&a[u-1][v]>t) { a[u-1][v]=t; res(u-1,v,i,j); } if(v>0&&map[u][v-1]!='#'&&a[u][v-1]>t) { a[u][v-1]=t; res(u,v-1,i,j); } if(u<n-1&&map[u+1][v]!='#'&&a[u+1][v]>t) { a[u+1][v]=t; res(u+1,v,i,j); } } int startx,starty,endx[10000],endy[10000],k=0,ans1=9999999,endx2,endy2,k2=0,ans2=9999999,sx,sy,ans3=9999999; int main() { while(scanf("%d%d",&n,&m)&&m!=0&&n!=0) { memset(a,1,sizeof(a)); memset(endx,0,sizeof(endx)); memset(endy,0,sizeof(endy)); for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { scanf("%c",&map[i][j]); if(map[i][j]=='@') { startx=i; starty=j; } if(map[i][j]=='*') { endx[k]=i; endy[k++]=j; } if(map[i][j]=='s') { endx2=i; endy2=j; } } getchar(); } memset(a,1,sizeof(a)); a[startx][starty]=0; for(int i=0;i<k;i++)//枚举 { memset(a,1,sizeof(a));//分段考虑 a[startx][starty]=0; res(startx,starty,endx[i],endy[i]); ans1=ans,sx=endx[i],sy=endy[i];//赋值 ans=0; memset(a,1,sizeof(a)); a[sx][sy]=0; res(sx,sy,endx2,endy2); ans2=ans; if(ans3>ans2+ans1) ans3=ans1+ans2//更新; ans=0; } if(ans3<9999999)cout<<ans3<<endl; else cout<<-1; ans3=9999999;//更新 ans1=9999999; ans2=9999999; k2=0; k=0; } }
好了,写完代码了。
如果你认为对了,那么可以输入这组数据试一试
20 50 @************************************************* ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** ************************************************** *************************************************s
按这样来说,每一个*都要被算一次,这样的复杂度即使是喜欢打暴力的人也是不能容忍的,当然,改进算法才是我们的重点,但是怎样优化呢?
用BFS???
不好吧,那也会超限,但是正解+BFS是很快的。
用DFS???
更差了……
我们先来想一想这样一个问题,如果一个人要从上海开车去北京旅游,他会把所有路都傻傻的走一遍吗?当然不会了,因为人类有一张王牌——地图,我们可以去估一估最短距离。
那么这道题也是一样的,我们可以大概估一个两点之间最短距离值,算出它以后,因为我们默认为两点之间没有障碍,所以它就是最短路了,如果这样算的话还是大于等于原来我们记录的那个最小值,那实际的路径一定比这个估算最优值大或等于,由此,我们可以去除多个药的枚举剪枝。
我们的代码只需要加上这么一行,就能大幅度提高效率(往往算法的精髓就在于此——剪枝):
if(abs(endx[i]+endy[i]-startx-starty)+abs(endx2+endy2-endx[i]-endy[i])<ans3)
【代码】
#include<iostream> #include<cstring> using namespace std; char map[101][101]; int a[101][101],ans=0,m,n; void res(int u,int v,int i,int j) { int t=a[u][v]; if(u==i&&v==j) ans=t; t++; if(v<m-1&&map[u][v+1]!='#'&&a[u][v+1]>t) { a[u][v+1]=t; res(u,v+1,i,j); } if(u>0&&map[u-1][v]!='#'&&a[u-1][v]>t) { a[u-1][v]=t; res(u-1,v,i,j); } if(v>0&&map[u][v-1]!='#'&&a[u][v-1]>t) { a[u][v-1]=t; res(u,v-1,i,j); } if(u<n-1&&map[u+1][v]!='#'&&a[u+1][v]>t) { a[u+1][v]=t; res(u+1,v,i,j); } } int startx,starty,endx[10000],endy[10000],k=0,ans1=9999999,endx2,endy2,k2=0,ans2=9999999,sx,sy,ans3=9999999; int main() { while(cin>>n>>m&&m!=0&&n!=0) { memset(a,1,sizeof(a)); memset(endx,0,sizeof(endx)); memset(endy,0,sizeof(endy)); for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { cin>>map[i][j]; if(map[i][j]=='@') { startx=i; starty=j; } if(map[i][j]=='*') { endx[k]=i; endy[k++]=j; } if(map[i][j]=='s') { endx2=i; endy2=j; } } } memset(a,1,sizeof(a)); a[startx][starty]=0; for(int i=0;i<k;i++) { if(abs(endx[i]+endy[i]-startx-starty)+abs(endx2+endy2-endx[i]-endy[i])<ans3)
//判定,如果此代码其它地方不理解可以去看仙岛求药(一) { memset(a,1,sizeof(a)); a[startx][starty]=0; res(startx,starty,endx[i],endy[i]); ans1=ans,sx=endx[i],sy=endy[i]; ans=0; memset(a,1,sizeof(a)); a[sx][sy]=0; res(sx,sy,endx2,endy2); ans2=ans; if(ans3>ans2+ans1) ans3=ans1+ans2; ans=0; } } cout<<ans3<<endl; ans3=9999999; ans1=9999999; ans2=9999999; k2=0; k=0; } }