细心和智慧并存的解答
题目链接
题的大致意思是能不能从起点经过T时间精准到达终点,图中有一些障碍不能走,走过的格子也不能再走。
其中数据是最大7*7的迷宫,最多50个迷宫。
智慧之处——精准的剪枝
类似全排列的深搜时间复杂度是O(n!),7*7的数据是过不去的。
首先就是普通地剪枝:
- 如果时间已经大于T,就不用再走了
- 如果已经有正确答案,要及时地退出搜索
然后是奇偶剪枝,这里参考了其他博客
这里我重新画了一张图,如下:
图中的S可以代表任何你走到的位置,不一定是起点。就如图中的红色和蓝色路径,这两条路径是最少需要的时间。如果剩余时间少于最短路径长度,那么是一定不能到达终点的。
既然时间不够一定不能到达,时间刚好和剩余最少时间相等也能到达,那么还剩一种情况,就是时间大于剩余最少时间。这也应该是大概率会遇到的情况。根据题意我们只能在第T秒精准到达终点,我们时间多出来的怎么办呢?——我们需要在终点周围“徘徊”。
如图中的绿色和黄色箭头所示,你比最短路径多走出来的路径,一定是先“出去”,再“回来”。多走的路径是“对称的”,那多出来的路径长度一定是偶数。
这就是奇偶性剪枝。加上这个剪枝就能过所有的测试数据。
细心之处——多数据下数组的重复利用
尽管我写过无数个全排列暴力骗过不少10分、20分。但是在这道题上还是因为粗心而浪费了不少时间。
在做杭电这前十道题的时候我就发现基本都是多数据的形式。所以除了输入会重新填入数据的数组外,其他数据都需要进行“初始化”。
但是即使我清空了vis
数据,我发现代码运行的答案竟然是错误的(样例是能过的)。
原因就是这道题毕竟不是全排列,vis数组不能只在dfs的for循环中去设置。你的起点在你走出第一步后就再也不能回来了。
vis[startX][startY]=true;
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include<iostream> #include<cstdio> #include<map> #include<cmath> #include<string> #include<algorithm> using namespace std;
char mp[10][10]; int n,m,T; bool vis[10][10];
int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0};
string ans;
int startX,startY,endX,endY; void dfs(int x,int y,int step){ if(ans=="YES") return; else if(step>T) return; else if(mp[x][y]=='D'&&step==T){ ans = "YES"; return; }
if(T-step<(abs(x-endX)+abs(y-endY))){ return; } if((T-step-(abs(x-endX)+abs(y-endY)))%2!=0){ return; } for(int i=0;i<4;i++){ int nx = x+dx[i]; int ny = y+dy[i]; if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&!vis[nx][ny]&&mp[nx][ny]!='X'){ vis[nx][ny]=true; dfs(nx,ny,step+1); vis[nx][ny]=false; } } } int main(){ while(1){ scanf("%d%d%d",&n,&m,&T); if(!n&&!m&&!T) break; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>mp[i][j]; if(mp[i][j]=='S'){ startX=i; startY=j; }else if(mp[i][j]=='D'){ endX=i; endY=j; } } } ans = "NO"; memset(vis,false,sizeof(vis)); vis[startX][startY]=true; dfs(startX,startY,0); cout<<ans<<endl; } return 0; }
|