DFS
剪枝
- 排除等效冗余
先搜1再搜2和先搜2再1相同,每次从上个搜索的地方搜起 - 数论优化
搜索过程中往往富含数学规律,常见的为搜索结果为某数的约数 - 搜索顺序优化
选择优秀的搜索顺序,比如从大到小搜索 - 可行性剪枝
搜索过程即可预判最终结果失败 - 记忆化搜索
将已搜索到的结果存起来,再次搜索可以直接得到答案 - 最优化剪枝
搜索过程即可以预判存在更优解或非最优解 - 上下界剪枝
搜索的起点和终点是有限制的 - 奇偶性剪枝
在接下来的题目中,将详细讲解以上剪枝的应用
UVA307 小木棍 Sticks
POJ1011
题意
给你若干根短棒
将其组合成等长的木棒,所有短棒都要用完
尽可能短,并输出其长度
分析
- 搜索顺序优化:从长的木棍开始搜索
- 数论优化:组成木棍长度必定为所有木棍长度的约数
- 可行性剪枝:第一根都用不上,必定失败
- 排除等效冗余::记录上次搜索地方,每次从上个地方搜起
- 上下界剪枝:等长木棒的长度至少为最短木棒,最长不超过木棒长度总和
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 70;
bool cmp(int a, int b) {
return a > b;
}
int n, sum, a[maxn];
bool vis[maxn];
int tot, maxl;
bool dfs(int s, int l, int ops) {
if (s == tot - 1)return true;
for (int i = ops; i <= n; i++) {
if (vis[i]) continue;
if (a[i] + l == maxl) {
vis[i] = true;
if (dfs(s + 1, 0, 1)) return true;
vis[i] = false;
return false;
}
else if (a[i] + l < maxl) {
vis[i] = true;
if (dfs(s, l + a[i], i + 1)) return true;
vis[i] = false;
if (l == 0) return false;
while (a[i] == a[i + 1]) i++;
}
}
return false;
}
int main() {
while (~scanf("%d", &n)&&n) {
sum = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
sum += a[i];
}
sort(a + 1, a + 1 + n, cmp);
for (int i = a[n]; i <= sum; i++) {
if (sum % i) continue;
tot = sum / i; maxl = i;
fill(vis, vis + 1 + n, 0);
if (dfs(0, 0, 1))
break;
}
printf("%d\n", maxl);
}
}
HDU1426 Sudoku Killer
题意
给出
9
∗
9
9*9
9∗9的数独(填入数字使得每行每列每个数字都不同)
且保证有且只有一个解
分析
- 搜索顺序优化:显然先搜索?多的行,可能的方案明显要多,先从?少的行先搜起
- 搜索顺序优化:预处理空格,只需要搜索空格即可
- 可行性优化:一旦不可行,就返回失败
- 可行性优化:预处理每行可用的数
一般用上前面一两个剪枝即可
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
typedef pair<int, int> pii;
int a[12][12], use[12][12];
pii num[12];
bool vis[12][12], can[12][12], f[12][12];
bool flag = false;
void dfs(int s, int x) { //num[s], 第x列
if (s == 10) { flag = true; return; }
int i = num[s].second;
for (int j = x; j <= 9; j++) {
if (a[i][j]) {
if (j == 9) {
dfs(s + 1, 1);
return;
}
else continue;
}
for (int k = 1; k <= use[i][0]; k++) {
if (can[i][k]) continue;
if (vis[j][use[i][k]])continue;
if (f[(i - 1) / 3 * 3 + (j - 1) / 3][use[i][k]])continue;
a[i][j] = use[i][k];
can[i][k] = true;
vis[j][use[i][k]] = true;
f[(i - 1) / 3 * 3 + (j - 1) / 3][use[i][k]] = true;
if (j == 9) dfs(s + 1, 1);
else dfs(s, j + 1);
if (flag) return;
a[i][j] = 0;
can[i][k] = false;
vis[j][use[i][k]] = false;
f[(i - 1) / 3 * 3 + (j - 1) / 3][use[i][k]] = false;
}
return;
}
}
int main() {
char s[100]; int g = 0;
while (1) {
memset(a, 0, sizeof(a)); //初始化
memset(vis, 0, sizeof(vis));
memset(f, 0, sizeof(f));
memset(use, 0, sizeof(use));
memset(can, 0, sizeof(can));
memset(num, 0, sizeof(num));
flag = false; char ch;
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (!(cin >> ch))return 0; //用cin输入
if (ch >= '0' && ch <= '9')a[i][j] = ch - '0';
if (ch == '?')
num[i].first++, a[i][j] = 0;
vis[i][a[i][j]] = true;
}
num[i].second = i;
}
for (int i = 1; i <= 9; i++) { //记录每行可用的数
for (int j = 1; j <= 9; j++) {
if (!vis[i][j]) use[i][++use[i][0]] = j;
}
}
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= 9; i++) { //记录每列和每个九宫格已有的数
for (int j = 1; j <= 9; j++) {
vis[i][a[j][i]] = true;
f[(i - 1) / 3 * 3 + (j - 1) / 3][a[i][j]] = true;
}
}
sort(num + 1, num + 10);
dfs(1, 1);
if (g++)printf("\n");
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 8; j++)
printf("%d ", a[i][j]);
printf("%d\n", a[i][9]);
}
}
}