HDU 1010 DFS+奇偶剪枝
Tempter of the Bone
做DFS(或其他搜索题),我感觉最有趣的地方不是DFS本身而是——“剪枝”。剪枝,顾名思义就是剪去不必要的枝节,也就是避免不必要的搜索过程。有点类似于工程领域的“去噪”,当然这是个人感觉而已。。
HDU 1010这道题是一个典型的迷宫搜索题。给你出口入口,但是你并不是能找到出口就完事了。注意事项:
- 必须在给定的时间 t 的时候找到。不能多于t 也不能少于 t
- 入口S 并不是每次都在 左上角(0,0)的位置,也可能是其他位置
分析DFS
前面有篇博文《hdu1518》,已经提到了DFS的一般框架就是:循环+递归。
循环,是横着走。递归是竖着走。注意,我这里说的“横”和“竖”并不是对应本题中迷宫的物理意义上的横竖,而是关于 抽象的逻辑意义上的横 和竖。
- 竖:是从某点出发,每一步都是走当前步的后继结点
- 横:用于“竖”走不通的时候,比如没有后继,或后继都搜索过的情况下,此时去搜索当前结点的兄弟结点,换句话说就是回溯到当前结点的父节点,再选取父节点中没有走过的“岔路”。
本题中 的 横 是 某一结点周围的 上、下、左、右 四个方向。
本题中的 竖 就是递归搜索每一结点的 “横”。
终止态:是所走的步数(即题意中的时间)等于给出的时间。若此时还未走到出口,则返回fasle。找到出口则返回true。
本题中我没有使用一个[4][2]的二维数组来保存 上下左右 四个方位的移动。所以看起来代码有些繁琐,但是可读性却高。
bool dfs(int ti,int i,int j) { if(ti==t)//ti是当前步数,t是给出的步数,或者说时间 { if(i==h&&j==w) return true; return false; } if(j<m-1&&!ma[i][j+1]) { ti++;//步数+1 ma[i][j]=true;//迷宫中该点设为已走 if(ti<=t&&dfs(ti,i,j+1)) return true; ti--;//若走此步最终失败了,那么就步数-1 ma[i][j]=false;//再设为该点未走,即回溯的过程 } if(i<n-1&&!ma[i+1][j]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i+1,j)) return true; ti--; ma[i][j]=false; } if(i&&!ma[i-1][j]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i-1,j)) return true; ti--; ma[i][j]=false; } if(j&&!ma[i][j-1]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i,j-1)) return true; ti--; ma[i][j]=false; } return false; }
剪枝
本题代码简单但是很容易超时,所以要注意剪枝。
首先看理论上的最短路径。
int path = abs(si-h)+abs(sj-w);
(si,ji)、(h,w)分别为起点终点坐标。我们可使用的两种剪枝方案是:
- 如果所给的时间(步数) t 小于最短步数path,那么一定走不到。
- 若满足t>path。但是如果能在恰好 t 步的时候,走到出口处。那么(t-path)必须是二的倍数。
关于第二种方案的解释:
这种方案学名为“奇偶剪枝”。我们已知了最短的步数就是直角三角形的两条直角边,实际上的路径却不一定非要沿着这两条边走的。仔细看看只要是移动方向一直是右、下,那么走到的时候总步数也一定是path的。然而由于墙的存在或许我们不可能一直右、下的走下去。为了避开墙,我们可能会向左走,向上走等等。但为了到达目的地,你在最短步数的基础上,如果向右走了一步,那么某一时候也必须再向左走一步来弥补。所以(t-path)一定要是2的倍数。
if(t<path||(t-path)%2) { cout<<"NO"<<endl; continue; }
看完整代码:
#include <iostream> using namespace std; bool ma[10][10]; int h,w; int n,m,t; bool dfs(int ti,int i,int j) { if(ti==t) { if(i==h&&j==w) return true; return false; } if(j<m-1&&!ma[i][j+1]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i,j+1)) return true; ti--; ma[i][j]=false; } if(i<n-1&&!ma[i+1][j]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i+1,j)) return true; ti--; ma[i][j]=false; } if(i&&!ma[i-1][j]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i-1,j)) return true; ti--; ma[i][j]=false; } if(j&&!ma[i][j-1]) { ti++; ma[i][j]=true; if(ti<=t&&dfs(ti,i,j-1)) return true; ti--; ma[i][j]=false; } return false; } int main() {//freopen("in.txt","r",stdin); while(cin>>n>>m>>t) { if(!n&&!m&&!t) break; memset(ma,false,sizeof(ma)); char c; int si,sj; for(int i=0;i<n;i++) for(int j=0;j<m;j++) { cin>>c; switch(c) { case 'S':si=i;sj=j;ma[i][j]=true;break; case '.':ma[i][j]=false;break; case 'X':ma[i][j]=true;break; case 'D':ma[i][j]=false;h=i,w=j;break; } } int path = abs(si-h)+abs(sj-w); if(t<path||(t-path)%2) { cout<<"NO"<<endl; continue; } if(dfs(0,si,sj)) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }