zoukankan      html  css  js  c++  java
  • 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)分别为起点终点坐标。我们可使用的两种剪枝方案是:

    1. 如果所给的时间(步数) t 小于最短步数path,那么一定走不到。
    2. 若满足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;
    }
    
  • 相关阅读:
    Linux服务器通过rz/sz轻松上传下载文件
    Linux卸载系统自带的JDK
    汉语-词语:恒等
    汉语-词语:女人
    汉语-词语:长远
    汉语-词语:长久
    汉语-词语:短暂
    汉语-词语:当下
    汉语-词语:漫长
    中药:小麦
  • 原文地址:https://www.cnblogs.com/unclejelly/p/4082086.html
Copyright © 2011-2022 走看看