众所周知,dfs是一个非常通俗易懂的算法,并且非常的简单,好学。不过dfs这个算法存在着一些缺点,例如说会超时,今天,教大家几个卡常技巧,可以让你在考试是写的dfs多拿一点分。
我们先来看一个简单的问题,如下:
有n种物质,第i种物质是有a[i]个a元素和b[i]个b元素,并且卖出c[i]元,我需要一个比例恰好为a1:b1的元素,而且我希望花的钱尽可能少。
这是数据范围:
n<=40
a和b的值<10,c的值<100,a1和b1<=10,而且所有数字都是整数。
好了,很多同学看到这个数据范围(n<=40)就慌了,因为我暴力枚举每一个选不选是2^40的时间复杂度,一般来讲,你跑到2^27以上就危险了,所以说我们要想办法优化一下。
这个东西,我们用的方法叫做最值剪枝(我自己发明的一个名字),我们先看一下暴力的正常做法:
#include <bits/stdc++.h>
using namespace std;
const int N = 50;
int n,m,k;
struct owl{
int a,b,c;
}x[N];
int res = 0x3f3f3f3f;
void dfs(int u,int owl1,int owl2,int s){
if (u == n){
if (owl1 * k == owl2 * m && owl1 != 0 && owl2 != 0){
res = min(res,s);
}
return ;
}
if (owl1 * k == owl2 * m && owl1 != 0 && owl2 != 0){
res = min(res,s);
return ;
}
dfs(u + 1,owl1,owl2,s);
dfs(u + 1,owl1 + x[u].a,owl2 + x[u].b,s + x[u].c);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n >> m >> k;
for (int i = 0; i < n; i ++ ){
cin >> x[i].a >> x[i].b >> x[i].c;
}
dfs(0,0,0,0);
if (res == 0x3f3f3f3f){
cout << -1;
return 0;
}
cout << res;
return 0;
}
对吧,是不是这样的?最值剪枝的意思是如果我当前的结果已经超过最小值(res)了,我就没必要继续搜索下去了,我们思考一下为什么?
证明:因为每个数都是正数,所以说你越搜索只会越大,到时候更行了还是当前的ren更小,所以说没有必要继续搜索下去,如下:
还有,这里面还用到了一个东西,叫做当前最优解定理,就是如果我当前已经满足条件了,也没必要搜索下去,因为我肯定少搜索一点我的结果更少。
然后那个最值剪枝只需要加上这一句:
if (s >= res){
return ;
}
就完成了。
还有一种剪枝,叫做性质剪枝,大概如下:
如果对于i来说,已经成为了死路一条的话(不管你怎么搜索,都不行了),那么我们就不搜索,这也是一个降低复杂度的很好的方法。
还有一种,非常重要,叫做记忆化搜索,主要是就相当于把该状态的值记录一下,最后继续搜索的时候直接用这个值就可以了,这样可以直接降低大量时间复杂度。
不过其实每一种方法都有优点和缺点,大家需要根据题目来。
这里附上一些记忆化的详细讲解:dp入门课(五)-CSDN博客