本文主要是介绍算法学习系列(四十五):DFS之剪枝与优化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 引言
- DFS之剪枝与优化
- 一、小猫爬山
- 二、木棒
- 三、数独
引言
关于这个 D F S DFS DFS 的剪枝和优化确实难度是非常的大,从我这篇文章的思路和代码量上就能看出来不是一般的难度,而且难度不亚于 D P DP DP ,而且这个 D F S DFS DFS 也是花费了我三天的时间才基本把这几道例题给搞懂了,并且这种题就是没有固定的模型和套路,每个题都不一样,只有你多做题,这样在考场上才能想到这道题好像跟之前做过的题有点相似,然后再去套用模型。不可能短时间去创造一种算法,所以还是继续刷题吧,加油!
DFS之剪枝与优化
- 优化搜索顺序:大部分情况下,我们应该优先搜索分支较少的节点。比如背包问题应该先放体积大的物品
- 排除等效冗余:组合问题中, 1 , 2 , 3 1,2,3 1,2,3 与 1 , 3 , 2 1,3,2 1,3,2 是一样的,可以用一个 s t a r t start start 来规定初始
- 可行性剪枝:发现当前状态不合法,退出
- 最优性剪枝:如果发现当前方案不可能是最优解了,就退出
搜索顺序模型:
1.给一堆物品及其单个体积和单个篮子的容量,问最小的篮子数。解决方案:枚举每个物品,该物品要么加入到已有的篮子里,要么新开一个篮子。
2.给一堆物品及其单个体积,问刚好能把所有篮子都装满的最小篮子容量。解决方案:枚举每个篮子,然后在每个篮子中枚举每个物品。
3.给一堆物品,装进篮子里,但都有一定的限制。解决方案:枚举每个物品
一、小猫爬山
标签:搜索、深度优先搜索、DFS
思路:
首先要保证正确性,也就是枚举的所有方案必须是不重不漏的,然后才对其进行优化,不能本末颠倒了。我们可以按小猫的编号进行枚举,当前小猫要么新坐一辆缆车,要么从已有的缆车里挑一个坐。首先是优化搜索顺序,可以把小猫的重量从大到小排序,进行枚举,然后就是如果当前的方案已经不是最优解了,直接退出,再有就是如果当前缆车已经容不下当前小猫了,退出。
题目描述:
翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。翰翰和达达只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。数据范围
1≤N≤18,1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 25;int n, m;
int w[N];
int sum[N];
int ans = N;void dfs(int u, int k) // 正在选编号为u的小猫,当前的组数为k
{if(k >= ans) return; // 最优性剪枝if(u == n) // u是从0开始的,这是说明已经全部选完了,的组数k {ans = k; return;}for(int i = 1; i <= k; ++i) // 要么从当前组里调一个组放进去{if(sum[i] + w[u] <= m) // 可行性剪枝{sum[i] += w[u];dfs(u+1,k);sum[i] -= w[u];}}sum[k+1] = w[u]; // 要么直接新开一个组dfs(u+1,k+1);sum[k+1] = 0;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);cin >> n >> m;for(int i = 0; i < n; ++i) cin >> w[i];sort(w, w + n, greater<int>()); // 搜索顺序优化dfs(0,0);cout << ans << endl;return 0;
}
二、木棒
标签:搜索、DFS、剪枝
思路:
首先这道题是找最小的木棒长度,也就是组数是无所谓的,只要能凑出来每组最小的容量即可。之前求最小组数的思路是,枚举每一个物品,然后这件物品,要么加入到当前任意一个组里,要么新开一个组,在此基础上进行剪枝优化。而这道题是从小到大枚举容量,如果当前容量恰好能装满每一个组,那就是答案了。然后在每一个组的基础上枚举每一件物品,放入该组里,如果当前组满了,然后再开下一个组,然后在此进行剪枝优化。首先是优化搜索顺序,先从大到小排序,然后最最小的也就是最长的那根木棍了。然后因为是每个组都要枚举每一件物品,所以避免出现组合型冗余,规定一个 s t a r t start start 枚举顺序,因为之前的已经枚举过了,如果继续从 0 0 0 开始,那么还要枚举重复的,比如说: 1 , 2 , 3 和 3 , 1 , 2 1,2,3和3,1,2 1,2,3和3,1,2 之类的方案。然后就是可行性剪枝了,就是如果当前组装不下,那就不装了,另外如果当前木棍是第一个装的,但没有满足要求,那么该类方案一定没有解,因为如果该木棍是下一组的,可以通过在组内排顺序把它变成第一个,那么上一次没有满足要求,这次也就不会满足要求了。其次如果该木棍是最后一根刚好装满了,但是之后的方案却没有满足,那么该方案之后的所有都不会满足的,因为如果满足要求了,那么肯定会有几根木棍凑出来最后一根,因为这两长度是一样的,所以可以替换,但因为之前的最后一根已经不满足了,所以肯定不会有满足要求的情况存在。
题目描述:
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。输入格式
输入包含多组数据,每组数据包括两行。第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。数据范围
数据保证每一节木棍的长度均不大于 50。输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 100;int n;
int w[N];
bool st[N];
int sum, len;bool dfs(int u, int cur, int start) // 当前u组里,目前的容量为cur,从start开始
{if(u * len == sum) return true; // 说明前u个组里已经满了,并且长度已经达到总和if(cur == len) return dfs(u+1,0,0); // 当前组已经满了,新开下一个组for(int i = start; i < n; ++i) // 排序冗余{if(st[i]) continue; // 可行性剪枝 只能用一次if(cur + w[i] <= len) // 可行性剪枝 不能超过容量{st[i] = true;if(dfs(u,cur+w[i],i+1)) return true;st[i] = false;}if(!cur || cur + w[i] == len) return false; // 可行性剪枝int j = i + 1;while(j < n && w[i] == w[j]) j++;i = j - 1;}return false;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);while(cin >> n, n){sum = 0, len = 0; for(int i = 0; i < n; ++i) {cin >> w[i];sum += w[i];len = max(len,w[i]);}sort(w, w + n, greater<int>());memset(st, 0, sizeof st);while(1){if(sum % len == 0 && dfs(0,0,0)){cout << len << endl;break;}len++;}} return 0;
}
三、数独
标签:搜索、深度优先搜索、DFS
思路:
这道题就是枚举每一个空格,如果最后没有空格了,说明正确直接返回。每一行每一列和每一个田字格可以用一个 9 9 9 位的二进制数来表示,每一位为 1 1 1 说明当前位是空位。然后优化顺序,可以每次找到最少可能的格子来先枚举,可以用与运算行列和当前的田字格,来知道当前格子的 9 9 9 位二进制状态,然后可以提前打表,知道这个数是有几个 1 1 1 组成的,然后枚举该格子的多种可能,然后可以用 l o w b i t lowbit lowbit 来知道最后一位 1 1 1 对应的数,然后在可以通过提前打表知道该数所对应的位,然后暴搜各种可能即可。
题目描述:
数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得数独中每行、每列、每个 3×3 的九宫格内数字
1∼9 均恰好出现一次。请编写一个程序填写数独。输入格式
输入包含多组测试用例。每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。您可以假设输入中的每个谜题都只有一个解决方案。文件结尾处为包含单词 end 的单行,表示输入结束。输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。输入样例:
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 9, M = 1 << 9;int ones[M], mmap[M];
int col[N], row[N], cell[3][3];
char str[100];void init()
{for(int i = 0; i < N; ++i) col[i] = row[i] = (1 << 9) - 1;for(int i = 0; i < 3; ++i){for(int j = 0; j < 3; ++j){cell[i][j] = (1 << 9) - 1;}}
}void draw(int x, int y, int t, bool is_set)
{if(is_set) str[x * N + y] = t + '1'; // 这里写1,是因为之后 -v 的缘故 else str[x * N + y] = '.';int v = 1 << t;if(!is_set) v = -v;row[x] -= v; // v得是从1开始的 已经设置了说明当前该位的1要变为0 col[y] -= v;cell[x/3][y/3] -= v;
}int get(int x, int y)
{return row[x] & col[y] & cell[x/3][y/3];
}int lowbit(int x)
{return x & -x;
}bool dfs(int cnt)
{if(!cnt) return true;int minv = 10, x, y;for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){if(str[i * N + j] == '.'){int state = get(i,j);if(ones[state] < minv) // 从里面找能填的最小的 {minv = ones[state];x = i, y = j;}}}}int state = get(x,y);for(int i = state; i; i -= lowbit(i)) // 枚举1的次数 {int t = mmap[lowbit(i)]; draw(x,y,t,true);if(dfs(cnt-1)) return true;draw(x,y,t,false); }return false;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);for(int i = 0; i < N; ++i) mmap[1 << i] = i;for(int i = 0; i < M; ++i){for(int j = 0; j < N; ++j){if(i >> j & 1) ones[i]++;}}while(cin >> str, str[0] != 'e'){init();int cnt = 0;for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){if(str[i * N + j] != '.'){int t = str[i * N + j] - '1';draw(i,j,t,true);}else cnt++;}}dfs(cnt);cout << str << endl; }return 0;
}
这篇关于算法学习系列(四十五):DFS之剪枝与优化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!