众所周知,搜索是个好东西,他能在很多时候(就是你不会正解打暴力的时候)派上用场。
然而搜索的时间复杂度实在是太高了,大多数都是指数级别的,这让人很是头疼
那么我来总结一下对搜索进行优化的技巧:剪枝
什么是剪枝
我们知道,搜索的进程可以看做遍历一棵搜索树的过程。而所谓的剪枝,就是通过某种判断,避免一些不必要的遍历过程。
形象的来说,就是剪掉搜索树上的一些枝条来达到减少遍历次数,缩短时间复杂度的效果
剪枝的原则
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; }
自己领悟吧