回溯深搜与剪枝初步

回溯算法也称试探法,一种系统的搜索问题的解的方法,是暴力搜寻法中的一种。回溯算法的基本思想是:从一条路往前走,能进则进。回溯算法解决问题的一般步骤:

  • 根据问题定义一个解空间,它包含问题的解
  • 利用适于搜索的方法组织解空间
  • 利用深度优先法搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索

回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

  找到一个可能存在的正确的答案

  在尝试了所有可能的分步方法后宣告该问题没有答案

在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算,因此通常只有对小规模输入问题求解的时候采用回溯法,并且用剪枝函数来提速。

使用了剪枝技术的回溯法是一个即带有系统性又带有跳跃性的搜索算法。回溯法在用来求问题的所有解时,要回溯到根,且根节点的所有子树都已被搜索遍才结束。而如果用来求问题的任一解时,只要搜索到问题的一个解就可以结束。简单的介绍过后,还是看一道暴经典的习题吧,题目取自HDU_1010:Tempter of the Bone

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
4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
0 0 0

Sample Output
NO
YES

一看是迷宫问题,一般就提示我们回溯+DFS:

#include<stdio.h> // 62MS
#include<stdlib.h>
int des_row, des_col;
int m, n, t;
bool escape;
int dir[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; // dirction:left, up, right, down
int map[7][7];
void dfs(int row, int col, int step);
int main(void)
{
    int start_row, start_col;

    int wall;
    while(scanf("%d%d%d", &n, &m, &t))
    {
        wall = 0;
        escape = false; // 每组测试数据都要重置escape的值,否则肯定WA
        if(n == 0 && m == 0 && t == 0)
            break;
        for(int row = 1; row <= n; row++)
            for(int col = 1; col <= m; col++)
            {
                scanf(" %c", &map[row][col]); // 注意%c前面的空格
                if(map[row][col] == ‘S‘)
                {
                    start_row = row;
                    start_col = col;
                }
                if(map[row][col] == ‘D‘)
                {
                    des_row = row;
                    des_col = col;
                }
                if(map[row][col] == ‘X‘)
                    wall++;
            }
        if(n*m - wall - 1 < t) // 定界剪枝,该枝不剪耗时546MS
        {
            printf("NO\n");
            continue;
        }
        map[start_row][start_col] = ‘X‘;
        dfs(start_row, start_col, 0);
        if(escape)
            printf("YES\n");
        else
            printf("NO\n");

    }

    return 0;
}

void dfs(int row, int col, int step) // 因为回溯需要修改step所以它不能定义成全局变量,本函数的三个变量都需要保存在栈中
{
    if(row == des_row && col == des_col && step == t)
    {
        escape = true;
        return;
    }
    if(escape) // 提高效率,缺少的话超时,根据题意只需要确定有无解即可,不必找全解
        return;
    if(row<1 || row>n || col<1 || col > m) // 出界
        return;
    int temp = (t-step) - ( abs(des_row-row) + abs(des_col-col) );
    if(temp < 0 || (temp&1)) // 定界剪枝+奇偶剪枝(缺少超时),&的优先级大于||(奇偶剪枝可放在main函数中,只需一次判断即可)
        return;
    for(int i = 0; i < 4; i++)
    {
        if(map[row+dir[i][0]][col+dir[i][1]] != ‘X‘)
        {
            map[row+dir[i][0]][col+dir[i][1]] = ‘X‘;
            dfs(row+dir[i][0], col+dir[i][1], step+1); // 不要写成step++
            map[row+dir[i][0]][col+dir[i][1]] = ‘.‘;
        }
    }
    return;
}

// 一点心得&牢骚:
// 全不全局是个问题,一方面传参数目要尽量少,另一方面又要避免全局变量过多引起混乱
// 考虑不周的话,DFS很容易就栈溢出或者数组下标越界,因此要考虑好深搜到什么时候终止
// 该代码C++过了,G++ WA,害我反复修改提交了几个小时.不得不喷一下,HDOJ怎么评测的:(

我们结合上述程序中定义的数据进行分析,最多max_step = n*m- wall - 1 步到达终点,如果t > max_step 问题显然无解。最少min_step = abs(des_row-row) + abs(des_col-col) 步到达终点,如果t < min_step,问题同样无解。

这样我们限定了min_step ≤ t ≤ max_step。据此可以进行定界剪枝。

至于上述程序中的奇偶剪枝,意思就是偶数步才能到达的点,奇数步绝不可能到达,反之亦然。而偶数 - 奇数 = 奇数; 偶数 - 偶数 = 偶数; 奇数 - 奇数 = 偶数。再根据整数底层的二进制表示形式可知,奇数的最后一位必为1,这就是程序中temp&1 的由来。

另外在说一点,关于出界判断if(row<1 || row>n || col<1 || col > m) 也可以不用,方法就是在迷宫的最外围全部设置成虚拟的墙。

All Rights Reserved.
Author:海峰:)
Copyright © xp_jiang.
转载请标明出处:http://www.cnblogs.com/xpjiang/p/4438300.html以上.
时间: 2024-11-03 01:33:23

回溯深搜与剪枝初步的相关文章

【深搜加剪枝五】HDU 1010 Tempter of the Bone

Tempter of the BoneTime Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 64326    Accepted Submission(s): 17567 Problem Description The doggie found a bone in an ancient maze, which fascinated him a l

hdu1455 Sticks 深搜 强剪枝

Sticks Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 6035    Accepted Submission(s): 1704 Problem Description George took sticks of the same length and cut them randomly until all parts becam

一本通例题埃及分数—题解&amp;&amp;深搜的剪枝技巧总结

一.简述: 众所周知,深搜(深度优先搜索)的时间复杂度在不加任何优化的情况下是非常慢的,一般都是指数级别的时间复杂度,在题目严格的时间限制下难以通过.所以大多数搜索算法都需要优化.形象地看,搜索的优化过程就像将搜索树上没用的枝条剪下来,因此搜索的优化过程又叫剪枝.剪枝的实质就是通过判断决定是否要沿当前枝条走下去. 二.搜索的剪枝必需遵循三个原则: 1.正确性(不能把正解排除,要不然搜什么呢?)2.准确性(尽可能把不能通向正解的枝条剪去)3.高效性(因为在每个枝条上都要进行一次判断,如果判断的复杂

深搜优化剪枝

之前做过不少深搜题,很多TLE,所以剪枝很重要,如何“未雨绸缪”,避免不必要的搜索树分支? 例题: 数的划分 将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5: 1,5,1: 5,1,1: 问有多少种不同的分法. 输出一个整数,即不同的分法. 由题意得,其分发出现重复数字组合则为同一种算法,那么保证1-k个数递增即可 代码: #include<bits/stdc++.h> using namespace std;

深搜+DP剪枝 codevs 1047 邮票面值设计

codevs 1047 邮票面值设计 1999年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 给定一个信封,最多只允许粘贴N张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1-MAX之间的每一个邮资值都能得到. 例如,N=3,K=2,如果面值分别为1分.4分,则在1分-6分之间的每一个邮资值都能得到(当然还有8分.9分和1

深搜的剪枝技巧

众所周知,搜索是个好东西,他能在很多时候(就是你不会正解打暴力的时候)派上用场. 然而搜索的时间复杂度实在是太高了,大多数都是指数级别的,这让人很是头疼 那么我来总结一下对搜索进行优化的技巧:剪枝 什么是剪枝 我们知道,搜索的进程可以看做遍历一棵搜索树的过程.而所谓的剪枝,就是通过某种判断,避免一些不必要的遍历过程. 形象的来说,就是剪掉搜索树上的一些枝条来达到减少遍历次数,缩短时间复杂度的效果 剪枝的原则 1.正确性 废话,你剪枝都把正确的答案剪了你还搜个啥呢? 2.准确性 尽可能多的剪去不需

HDOJ/HDU Tempter of the Bone(深搜+奇偶性剪枝)

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 despe

HDU--1010 Tempter of the Bone(深搜+奇偶剪枝)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1010 我认为的剪枝就是在本来的代码中加入一些附加条件使之不去进行多余的计算,防止超时 奇偶剪枝的知识链接 AC代码: 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 int n,m,t,k,flag,starex,starey,endx,endy

HDU 1010 深搜+奇偶剪枝

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1010 贴个资料: http://acm.hdu.edu.cn/forum/read.php?tid=6158 奇偶剪枝: 对于从起始点 s 到达终点 e,走且只走 t 步的可达性问题的一种剪枝策略. 如下列矩阵 : 从任意 0 到达 任意 0,所需步数均为偶数,到达任意 1 ,均为奇数.反之亦然 所以有,若s走且只走 t 步可到达e,则必有t >= abs(s.x - e.x) + abs(s.y -