【HDOJ】「1010」 DFS+奇偶剪枝

原题出处

【Problem Description】

The doggie found a bone in an ancient maze, which fascinated him a
lot. However, when he picked it up, the maze began to shake, and the
doggie could feel the ground sinking. He realized that the bone was a
trap, and he tried desperately to get out of this maze. The maze was a
rectangle with sizes N by M. There was a door in the maze. At the
beginning, the door was closed and it would open at the T-th second
for a short period of time (less than 1 second). Therefore the doggie
had to arrive at the door on exactly the T-th second. In every second,
he could move one block to one of the upper, lower, left and right
neighboring blocks. Once he entered a block, the ground of this block
would start to sink and disappear in the next second. He could not
stay at one block for more than one second, nor could he move into a
visited block. Can the poor doggie survive? Please help him.

【Input】

The input consists of multiple test cases. The first line of each test
case contains three integers N, M, and T (1 < N, M < 7; 0 < T < 50),
which denote the sizes of the maze and the time at which the door will
open, respectively. The next N lines give the maze layout, with each
line containing M characters. A character is one of the following:
‘X’: a block of wall, which the doggie cannot enter; ‘S’: the start
point of the doggie; ‘D’: the Door; or ‘.’: an empty block. The input
is terminated with three 0’s. This test case is not to be processed.

【Output】

For each test case, print in one line “YES” if the doggie can survive,
or “NO” otherwise.

【Sample Input&Output】

4 4 5
S.X.
…X.
…XD

NO
3 4 5
S.X.
…X.
…D
YES

—————————————题解分割线————————————————

【读题思考】

  • 小狗能够逃脱的瞬间条件:小狗从起点开始到达到门的时间恰好为t;
  • 小狗能够逃脱的过程条件:不过墙不越墙不越界并存在一条路使得总时间能够为t;
  • 小狗的行动条件:仅能在上下左右中运动,且不能走回头路
  • 连对角线都走不了就不要想蛇形走位了
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<set>
#include<cmath>
using namespace std;
		//将对所有函数都需要的题设所需变量设为全局变量
char map[9][9];
int n,m,t;
di,dj;
bool escape;						//用于判断能否顺利逃出
int dir[4][2]={{0,-1},{0,1},{-1,0},{1,0}};	//可以前进的四个方向

【分析】

  • 对于只能在上下左右中运动的条件,我们可以讲场景方阵转化为一个“01”数字方阵,如:
    0101010101010101
    1010101010101010
    0101010101010101
    1010101010101010
    0101010101010101
    1010101010101010
    ————————————————
    从中我们可以看出所有0被1包围,而所有1被0包围,固每一步均是从0到1或是从1到0;又因为始末位置固定,无论中间过程如何(也无论可不可行只要能到达终点),都必须是…->0->1->0->1->…的形式,固同一始末位置的情况下,所有路程所需的时间奇偶性相同。

    利用这一性质,我们可以进行奇偶剪

int main(){
    int i,j,si,sj,temp;
    while(cin>>n>>m>>t){		
        if(n==0&&m==0&&t==0) break;	//特殊输入情况注意
        int wall=0;
        for(i=1;i<=n;i++){
            for(j=1;j<=m;j++){
                cin>>map[i][j];
                if(map[i][j]=='S'){si=i;sj=j;}
                else if(map[i][j]=='D'){di=i;dj=j; }
                else if(map[i][j]=='X'){ wall++;}
            }						//定位S和D,顺便利用wall进行初始剪枝(判断所有’.’(绝对可行位置)的格数是否够t使用)(即以下if的操作)
        }
        if(n*m-wall<=t){
            cout<<"NO"<<endl;
            continue;
        }
        temp=abs(t-abs(si-di)-abs(sj-dj));
        if(temp%2==1) {
            cout<<"NO"<<endl;   //剩余时间和网格最短距离的差,若可能可以则差必为偶数(奇偶剪)
            continue;
        }
        ——————————————以上即为奇偶剪的体现————————————————
        escape=0;
        map[si][sj]='X';		//起始位置必定不能再走
        dfs(si,sj,0);			//对起始位置的调用
        if(escape) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

  • 对于寻找可行路线,可能很多人最开始的想法就是将所有可能一一枚举然后进行判断,先且不论一一枚举是否会超时,光是利用一般的循环方法也很能穷尽所有可能,这时我们会想到使用递归,不同于一般的递归题目(线性等),这些个体之间既不是前驱后继的顺序关系,也不是祖先后代的层次关系,而是错综复杂的网状关系。

    利用这一点,我们可以通过DFS求连通块:通过DFS进行递归深搜,(本质上就是枚举)
    根据小狗不能走回头的特点,我们可以将已经走过的位置设为与原始“墙”一样的‘X’,从而对其进行标记。

    dfs函数如下

void dfs(int si,int sj,int cnt){          //对于每一种前进情况,当下的位置即可表示为si,sj,cnt表示已经用掉的时间
    int i,temp;                          //先写递归终点的情况
    if(si>n||sj>m||si<=0||sj<=0) return;  //除去超出边界的情况
    if(cnt==t&&si==di&&sj==dj) escape=1;	//到达终点并且时机恰好
    if(escape) return;
    temp=(t-cnt)-abs(si-di)-abs(sj-dj);	//判断剩余时间是否还够用
    if(temp<0) return;
    for(i=0;i<4;i++){		
        if(map[si+dir[i][0]][sj+dir[i][1]]!='X'){	//数组嵌套用法注意
            map[si+dir[i][0]][sj+dir[i][1]]='X';	//对于该假设路线下的标记
            dfs(si+dir[i][0],sj+dir[i][1],cnt+1);	//对当前位置的调用(不断调用至终点)
            map[si+dir[i][0]][sj+dir[i][1]]='.';		//若返回值这一步了说明上面的return一个都没实现,即此条路上的调用全部失败,将之前坐过的标记全部复原;
        }
    }
    return;
}

以上就是所有对小狗能否逃脱的判断,从简到繁,现在主函数中对显而易见的(如此处的奇偶性)不满足情况进行排除,再调用dfs进行深度搜索。

ps:当然奇偶剪也可以放在dfs函数中,只不过由上面的理由我们可以得知若在dfs调用时一次成立则次次成立,即会存在无意义的重复操作,固将其放到主函数中。

将程序的所有部分拟合一下就能够得到这道题的最终代码了(≧▽≦)/

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<set>
#include<cmath>
using namespace std;
char map[9][9];
int n,m,t,di,dj;
bool escape;
int dir[4][2]={{0,-1},{0,1},{-1,0},{1,0}};
void dfs(int si,int sj,int cnt);

int main(){
    int i,j,si,sj,temp;
    while(cin>>n>>m>>t){		
        if(n==0&&m==0&&t==0) break;
        int wall=0;
        for(i=1;i<=n;i++){
            for(j=1;j<=m;j++){
                cin>>map[i][j];
                if(map[i][j]=='S'){si=i;sj=j;}
                else if(map[i][j]=='D'){di=i;dj=j; }
                else if(map[i][j]=='X'){ wall++;}
            }				
        }
        if(n*m-wall<=t){
            cout<<"NO"<<endl;
            continue;
        }
        temp=abs(t-abs(si-di)-abs(sj-dj));
        if(temp%2==1) {
            cout<<"NO"<<endl; 
            continue;
        }
        escape=0;
        map[si][sj]='X';		
        dfs(si,sj,0);			
        if(escape) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}
void dfs(int si,int sj,int cnt){         
    int i,temp;                        
    if(si>n||sj>m||si<=0||sj<=0) return;  
    if(cnt==t&&si==di&&sj==dj) escape=1;	
    if(escape) return;
    temp=(t-cnt)-abs(si-di)-abs(sj-dj);	
    if(temp<0) return;
    for(i=0;i<4;i++){		
        if(map[si+dir[i][0]][sj+dir[i][1]]!='X'){	
            map[si+dir[i][0]][sj+dir[i][1]]='X';	
            dfs(si+dir[i][0],sj+dir[i][1],cnt+1);	
            map[si+dir[i][0]][sj+dir[i][1]]='.';		
        }
    }
    return;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值