搜索中的剪枝
一般来说剪枝有以下几类
(1)优化搜索顺序
比如一些有多个物品然后凑重量的题就可以重量大的优先,根据重量排序
(2)排除等效冗余
这是我最容易忽略的一点。在拼木棍那题中有淋漓尽致的体现
(3)可行性剪枝
如果当前无论如何都无法到达递归边界就剪掉
(4)最优性剪枝
这个就很常见了,形如if(now >= ans) return
因为无论如何都无法有更优的答案
(5)记忆化
如果会重复遍历状态,那就可以用记忆化
几道例题
poj 1011
http://poj.org/problem?id=1011
非常经典的一道题,拼木棍
枚举答案len,用搜索去判断能不能拼成
(1)优化搜索顺序
长度从大到小排序
(2)排除等效冗余
先用a再用b和先用b再用a是一样的,所以可以规定从上一根木棍的下一根开始拼
如果一根木棍去拼不能拼成,那么之后同样长度的木棍一样也不能,要剪去
如果当前已经拼了的长度为0,而又无法拼成,那么剪掉,因为这个时候木棍的选择是最多的,这个时候都不行,继续下去木棍选择变少更不行
#include<cstdio> #include<algorithm> #include<functional> #include<cstring> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; const int MAXN = 100 + 10; int a[MAXN], vis[MAXN], len, n, num; bool dfs(int cnt, int last, int now) { if(cnt == num) return true; if(now == len) return dfs(cnt + 1, 1, 0); int fail = 0; //剪枝 _for(i, last, n) //剪枝 if(!vis[i] && now + a[i] <= len && fail != a[i]) { vis[i] = 1; if(dfs(cnt, i + 1, now + a[i])) return true; vis[i] = 0; fail = a[i]; if(now == 0) return false; ////剪枝 } return false; } int main() { while(~scanf("%d", &n) && n) { int sum = 0, maxt = 0; _for(i, 1, n) { scanf("%d", &a[i]); sum += a[i]; maxt = max(maxt, a[i]); } sort(a + 1, a + n + 1, greater<int>()); //剪枝 for(len = maxt; len <= sum; len++) //枚举答案 if(sum % len == 0) { num = sum / len; memset(vis, 0, sizeof(vis)); if(dfs(0, 1, 0)) break; } printf("%d\n", len); } return 0; }
[NOI1999]生日蛋糕
https://www.luogu.org/problemnew/solution/P1731
这道题自己独立做的时候拿了70分,少想到了一个可行性剪枝
总结一下
(1)对于最优性剪枝,可以预处理出剩下情况下最优的答案来判断
(2)关于可行性剪枝,可以从最大和最小来判断。剩余情况最大都小于目标状态,剪掉,
剩余状况最小都大于,目标状态,剪掉
#include<cstdio> #include<algorithm> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= (b); i++) using namespace std; int n, m, ans, v[25], g[25]; void dfs(int cnt, int R, int H, int V, int S) { int left = m - cnt; if(S + g[left] >= ans) return; //最优性剪枝 if(V + v[left] > n) return; //可行性剪枝 if(V + R * R * H * left < n) return; //可行性剪枝,这个我漏了 if(cnt == m) { if(V == n) ans = S; return; } for(int r = R - 1; r >= left; r--) //可行性剪枝 for(int h = H - 1; h >= left; h--) //可行性剪枝 if(V + r * r * h <= n) dfs(cnt + 1, r, h, V + r * r * h, S + 2 * r * h); } int main() { scanf("%d%d", &n, &m); _for(i, 1, 20) v[i] = i * i * i; _for(i, 1, 20) g[i] = 2 * i * i; ans = 1e9; for(int h = n / (m * m); h >= m; h--) for(int r = m; r * r * h <= n; r++) dfs(1, r, h, r * r * h, r * r + 2 * r * h); printf("%d\n", ans == 1e9 ? 0 : ans); return 0; }