剪枝,就是减小搜索树规模、尽早排除搜索树中不必要的分支的一种手段。形象地看,就好像剪掉了搜索树的枝条,故被称为“剪枝”。在深度优先搜索中,有以下几类常见的剪枝方法:
1、优化搜索顺序
在一些搜索问题中,搜索树的各个层次、各个分支之间的顺序不是固定的。不同的搜索顺序会产生不同的搜索树形态,其规模大小也相差甚远。例如:
(1)在上一节的“小猫爬山”问题中,把小猫按照重量递减的顺序进行搜索。
(2)在上一节的“Sudoku”问题中,优先搜索“能填的合法数字”最少的位置。
2、排除等效冗余
在搜索过程中,如果我们能够判定从搜索树的当前节点上沿着某几条不同分支到达的子树是等效的,那么只需要对其中的一条分支执行搜索。我们会在本节的“Sticks”问题中看到该剪枝的应用。
另外,就如我们在上一节的“Sudoku”问题中提出的,初学者一定要避免重叠、混淆“层次”与“分支”,避免遍历若干棵覆盖统一状态空间的等效搜索树。
3、可行性剪枝
在搜索过程中,及时对当前状态进行检查,如果发现分支已经无法到达递归边界,就执行回溯。这就好比我们在道路上行走时,远远看到前方是一个死胡同,就应该立即折返绕路,而不是走到路的尽头再返回。
某些题目条件的范围限制是一个区间,此时可行性剪枝也被称为“上下界剪枝”。
4、最优性剪枝
在最优化的搜索过程中,如果当前花费的代价已经超过了当前搜到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案。此时可以停止对当前分支的搜索,执行回溯。
5、记忆化
可以记录每个状态的搜索结果,在重复遍历一个状态时直接检索并返回。这就好比我们对图进行深度优先遍历时,标记一个节点是否已经被访问过。
不过,读者可能已经发现,在“小猫爬山”与“Sudoko”问题中,我们的搜索算法遍历的状态空间其实是**“树“型,不会重复访问**,所以不需要进行记录。
题意 :
思路 :
#include
#include
#include
using namespace std;
const int N = 70;
int n, sticks[N];
bool st[N];
int sum, length;
bool dfs(int u, int cur, int start) {
if (u * length == sum) return true;
if (cur == length) return dfs(u + 1, 0, 0);
for (int i = start; i < n; ++ i) {
if (st[i]) continue;
int l = sticks[i];
if (cur + l <= length) {
st[i] = true;
if (dfs(u, cur + l, i)) return true;
st[i] = false;
if (!cur) return false;
if (cur + l == length) return false;
int j = i;
while (j < n && sticks[j] == l) ++ j;
i = j - 1;
}
}
return false;
}
int main() {
while (scanf("%d", &n), n) {
memset(st, 0, sizeof st);
sum = 0, length = 0;
for (int i = 0; i < n; ++ i) {
scanf("%d", &sticks[i]);
if (sticks[i] > 50) continue;
sum += sticks[i];
length = max(length, sticks[i]);
}
sort(sticks, sticks + n);
reverse(sticks, sticks + n);
for (int i = 0; i < n; ++ i) {
if (sticks[i] > 50) {
st[i] = true;
}
}
while (true) {
if (sum % length == 0 && dfs(0, 0, 0)) {
printf("%d\n", length);
break;
}
else length ++ ;
}
}
}
题意 :
思路 :
#include
#include
using namespace std;
const int N = 25, INF = 1e9;
int n, m;
int R[N], H[N];
int minv[N], mins[N];
int ans = INF;
void dfs(int u, int v, int s) {
if (v + minv[u] > n) return ;
if (s + mins[u] >= ans) return ;
if (s + 2 * (n - v) / R[u + 1] >= ans) return ;
if (!u) {
if (v == n) ans = s;
return ;
}
for (int r = min(R[u + 1] - 1, (int)sqrt(n - v)); r >= u; -- r) {
for (int h = min(H[u + 1] - 1, (n - v) / r / r); h >= u; -- h) {
int t = 0;
if (u == m) t = r * r;
H[u] = h; R[u] = r;
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
}
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; ++ i) {
minv[i] = minv[i - 1] + i * i * i;
mins[i] = mins[i - 1] + 2 * i * i;
}
R[m + 1] = INF, H[m + 1] = INF;
dfs(m, 0, 0);
if (ans == INF) cout << 0;
else cout << ans;
}