经典搜索(深搜+剪枝+Java细节注意)

本文探讨了一道关于迷宫求生的问题,利用深度优先搜索算法解决迷宫问题,特别关注剪枝技巧的应用,包括判断输入地图可行步数、最短距离奇偶性等,最终实现高效求解。

Tempter of the Bone

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 98309 Accepted Submission(s): 26658

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

3 4 5
S.X.
..X.
…D
0 0 0

Sample Output
NO
YES

  1. 刚开始看这道题的时候,乍一看像是迷宫求最短路径的题,直接用广搜做了,然而持续wa。后来仔细读题发现,题目要求doggie在第Tth second恰好到达Door才能获救,并不是最短路径。所以一定要仔细读题,尤其是遇到英文时,明确题意,否则就只能呵呵!
  2. 然而当我撸了一个深搜以后,oj却是TLD,设计了一个测试样例,居然发现跑到了三十几万毫秒,然而oj却要求2000ms
  3. 当我参考了网上的代码,进行了如下剪枝:判断输入的地图中能走的步数是否有可能为T的剪枝(如果地图中的最大步数小于T,肯定要剪掉)、判断出发点和目的地的最短距离是否小于T的剪枝(如果最短距离都大于T了,当然要剪枝了)、奇偶性剪枝。
  4. 做完上述这些以后,我信心勃勃的去提交,仍然为wa(而且是多次)。万般无奈之下,我一步一步的对着参考代码调试,终于让我发现了两个bug:一是Java中的foreach循环只能遍历集合或数组中的元素,并不能进行改动。二是不满足图中能走步数可能为T的剪枝时,continue后,没有了输入!!所以,设计测试样例,真是个技术活!

题目要求不能走重复的点,所以开一个数组vis进行标记,注意搜索过程中要恢复现场!
Whatever,通过这道题,学到了不少东西。这实际是一道司空见惯的深搜题,最为关键的是剪枝的技巧学习!!


> 

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int N,M,T;
    static int sx,sy,dx,dy;
    static char[][] maze = new char[10][10];
    static boolean[][] vis = new boolean[10][10];
    static int[] dirx={1,0,-1,0},diry={0,1,0,-1};
    static boolean flag;
    static void Debug(){
        System.out.println("Test");
    }
    static boolean Judge(){
        if(N==0&&M==0&&T==0) return false;
        return true;
    }
    static void dfs(int x,int y,int s){
        if(flag||s>T) return ;
        if(s==T){
            if(x==dx&&y==dy) flag = true;
            return ;
        }
        // even-odd prune
        int l = Math.abs(dx-x)+Math.abs(dy-y);
        if((l>T-s)||((T-s-l)%2!=0)) return ;
        for(int k=0;k<4;k++){
            int nx = x+dirx[k];
            int ny = y+diry[k];
            if(nx>=0&&nx<N&&ny>=0&&ny<M&&maze[nx][ny]!='X'&&!vis[nx][ny]){
                vis[nx][ny] = true;
                dfs(nx, ny,s+1);
                vis[nx][ny] = false;
            }
        }

    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner in = new Scanner(System.in);
        N = in.nextInt();
        M = in.nextInt();
        T = in.nextInt();
        String line = new String();
        while(Judge()){
            for(int i=0;i<N;i++){
                line = in.next();
                maze[i]=line.toCharArray();
            }
            int num1 = 0;
            for(int i=0;i<N;i++){
                for(int j=0;j<M;j++){
                    if(maze[i][j]=='S'){
                        sx = i;
                        sy = j;
                    }
                    if(maze[i][j]=='D'){
                        dx = i;
                        dy = j;
                    }
                    if(maze[i][j]=='X') num1++;
                }
            }
            if(N*M-num1-1<T||(T-Math.abs(sx-dx)-Math.abs(sy-dy))%2!=0){
                System.out.println("NO");
                N = in.nextInt();
                M = in.nextInt();
                T = in.nextInt();
                continue;
            }
            flag = false;
            for(int i=0;i<10;i++){
                for(int j=0;j<10;j++) vis[i][j] = false;
            }
            vis[sx][sy] = true;
            dfs(sx, sy,0);
            if(flag) System.out.println("YES");
            else System.out.println("NO");
            N = in.nextInt();
            M = in.nextInt();
            T = in.nextInt();
        }
    }

}

奇偶剪枝
奇偶剪枝:
起点S(sx,sy),终点D(ex,ey),给定t步恰好走到终点。
定理:S、D这两点之间的最短距离是step1=abs(ex-sx)+abs(ey-sy)!(走法:(sx,sy)->(sx,ey)->(ex,ey) 等等
对于一般非最短路径下的走法其距离记为step2,则step2-step1一定为偶数
推广:若 t-[abs(ex-sx)+abs(ey-sy)] 结果为非偶数(奇数),则无法在t步恰好到达;

度优先搜索(DFS)是一种常见的递归搜索算法,广泛应用于图遍历、组合问题、路径查找等领域。然而,DFS的搜索空间通常较大,尤其是在问题规模较大时,容易导致指数级的时间复杂度。为了提升效率,剪枝优化成为DFS算法中不可或缺的手段。 ### 可行性剪枝 可行性剪枝是一种在搜索过程中提前判断当前状态是否可能通向有效解的方法。例如,在路径搜索问题中,如果当前路径已经违反了某些约束条件,如超出边界或重复访问节点,则可以立即停止该路径的搜索。这种剪枝方式可以显著减少无效的递归调用,从而降低时间复杂度。例如,在解决“奇怪的电梯”问题时,若当前楼层加上或减去电梯按钮的值后超出了合法范围,则可以直接跳过该操作,避免不必要的递归调用[^5]。 ### 最优性剪枝 最优性剪枝适用于最优化问题,其核心思想是:在搜索过程中,如果当前路径的代价已经超过了已知的最优解,则可以提前终止该路径的搜索。例如,在最短路径问题中,如果当前路径的长度已经超过了当前记录的最短路径长度,则继续搜索下去只会浪费时间。这种方式可以有效减少搜索树的分支数量,提高搜索效率[^2]。 ### 搜索顺序剪枝 搜索顺序对DFS的效率有重要影响。通过调整搜索顺序,优先处理更可能找到解的分支,可以减少无效搜索。例如,在数独问题中,优先填充候选数较少的格子,可以更快地缩小搜索空间。这种方式不仅减少了递归的层数,还提高了剪枝的有效性[^4]。 ### 排除等效冗余 在某些问题中,不同的搜索路径可能产生相同的中间结果。为了避免重复计算,可以通过记录已经处理过的状态来排除等效冗余。这种方法类似于动态规划中的记忆化技术,能够显著减少重复计算的次数。例如,在组合问题中,若某组元素的排列已经处理过,则后续相同的排列可以直接跳过[^2]。 ### 记忆化搜索 记忆化搜索是将已经计算过的结果保存下来,以便在后续的搜索过程中直接使用。这种方式可以避免重复计算,特别是在递归过程中存在大量重叠子问题的情况下。例如,在求解斐波那契数列时,通过记忆化技术可以将时间复杂度从指数级降低到线性级别[^2]。 ### 剪枝与优化的平衡 剪枝虽然可以显著减少搜索次数,但同时也增加了判断条件的复杂度。因此,在设计剪枝策略时,需要权衡剪枝的准确性与判断的效率。过于复杂的剪枝条件可能会导致额外的时间开销,反而适得其反。理想情况下,剪枝条件应尽可能简单且高效,确保在减少搜索次数的同时,不会引入过多的计算负担[^3]。 ### 示例代码 以下是一个简单的DFS剪枝示例,展示了如何在路径搜索中应用可行性剪枝和最优性剪枝: ```cpp #include <iostream> #include <vector> using namespace std; const int MAX = 100; int visited[MAX]; int min_steps = 1e9; void dfs(int current, int target, int steps, vector<vector<int>>& graph) { if (steps >= min_steps) return; // 最优性剪枝 if (current == target) { min_steps = steps; return; } for (int neighbor : graph[current]) { if (!visited[neighbor]) { visited[neighbor] = 1; // 标记已访问 dfs(neighbor, target, steps + 1, graph); visited[neighbor] = 0; // 回溯 } } } int main() { int n, m, start, end; cin >> n >> m >> start >> end; vector<vector<int>> graph(n + 1); for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; graph[u].push_back(v); graph[v].push_back(u); } visited[start] = 1; dfs(start, end, 0, graph); cout << (min_steps != 1e9 ? min_steps : -1) << endl; return 0; } ``` 在上述代码中,`dfs`函数通过判断当前步数是否超过已知的最小步数(最优性剪枝),以及是否访问过某个节点(可行性剪枝),有效地减少了搜索次数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值