深搜的剪枝技巧

众所周知,搜索是个好东西,他能在很多时候(就是你不会正解打暴力的时候)派上用场。

然而搜索的时间复杂度实在是太高了,大多数都是指数级别的,这让人很是头疼

那么我来总结一下对搜索进行优化的技巧:剪枝

什么是剪枝

我们知道,搜索的进程可以看做遍历一棵搜索树的过程。而所谓的剪枝,就是通过某种判断,避免一些不必要的遍历过程。

形象的来说,就是剪掉搜索树上的一些枝条来达到减少遍历次数,缩短时间复杂度的效果

剪枝的原则

1.正确性

  • 废话,你剪枝都把正确的答案剪了你还搜个啥呢?

2.准确性

  • 尽可能多的剪去不需要的枝条,这样能够最大限度的优化搜索

3.高效性

  • 由于你剪枝之前都要判断是否可以剪枝,所以判断的复杂度当然很重要。
  • 如果你判断的复杂度都爆了,那你还剪什么枝?

然后就会出现一个矛盾。

我们如果想要尽可能优的剪枝,就必须提高判断的准确性,就是尽可能只要一发现就剪去,这就常常会提高判断的复杂度,同时就降低了剪枝的效率

但是如果剪枝消耗的时间过多,就会降低判断的准确性所带来的优化

所以我们很多时候都要考虑这两者之间的关系,看看如何处理

剪枝的技巧

1.优化搜索顺序

  • 在很多时候,不同的搜索顺序往往会产生不同形状的搜索树,而我们就需要尽可能的优化搜索树的形态

2.排除等效冗余

  • 有的时候我们可以从不同的分支到达同一个结果,那么这样的分支显然就只需要通过一次就可以了

3.可行性剪枝

  • 简单的来说就是尽可能早的发现不可能的情况并进行剪枝,也就是在判断的时候进行优化
  • 有的时候这个限制条件会以区间的形式出现,所以它也叫做“上下界剪枝”

4.最优性剪枝

  • 在最优化问题的过程中,如果当前的代价已经小于目前搜索到的最优解,显然就不用继续下去了,那么我们可以停止搜索,直接回溯

5.记忆化

  • 可以消耗空间来换取时间,就是记录每个状态的搜索结果,这样在遍历时就可以直接查询了。
  • 目前的比赛一般都是空间足够,时间卡得很紧,所以用空间来换取时间的方法是可取的(毕竟都说T成狗,你什么时候听说过M成狗的

我们来看一个比较简单的例题

数的划分:传送门

这道题最简单的方法显然就是一位一位的枚举,然后判断是否成立,是否和之前的情况重合。

显然会TLE

但是我们发现这个东西是不考虑顺序的,也就是说我们可以指定一个顺序,然后只要存储下来个数就可以了

不妨将方案从小到大,所以拓展节点时的下界就是前一个节点

上界是什么?

假设我们已经枚举到了第n-1个点,正在枚举第n个点,由于后一个数都是大于等于前一个数的

所以取最极端的情况,就是第n-1个数后面的所有数都相等,这时求平均数,显然就是第n个数的上界,因为它无论如何也不可能突破这个上界的

搜索的代码还是比较好些的

这里再提供另一种做法

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int n,k,f[201][7];  
int main(){
    cin>>n>>k;
    for (int i=1;i<=n;i++) {f[i][1]=1;f[i][0]=1;}
    for (int j=2;j<=k;j++) {f[1][j]=0;f[0][j]=0;}  
    for (int i=2;i<=n;i++)
        for (int j=2;j<=k;j++)
            if (i>j) f[i][j]=f[i-1][j-1]+f[i-j][j];
            else f[i][j]=f[i-1][j-1];
    cout<<f[n][k];
    return 0;
}

自己领悟吧

原文地址:https://www.cnblogs.com/lcezych/p/10990200.html