1 介绍
本专题用来记录使用dfs剪枝技巧求解的题目。
剪枝有以下思路:
- 优化搜索顺序。
- 可行性剪枝。
- 最优性剪枝。
- 唯一性剪枝,也叫去除冗余。
- 记忆化搜索,也叫dp。
2 训练
题目1:165小猫爬山
C++代码如下,
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 20;
int n, m;
vector<int> a;
vector<vector<int>> group;
int res = 20;
bool check(int x, int j) {
int s = 0;
for (auto v : group[j]) s += v;
return s + x <= m;
}
void dfs(int i, int groupsize) {
if (groupsize >= res) {//最优性剪枝
return;
}
if (i == n) {
res = groupsize;
}
int x = a[i];
//将x放入哪个组
for (int j = 0; j < groupsize; ++j) {
//将x放入第j组
if (check(x, j)) { //可行性剪枝
group[j].emplace_back(x);
dfs(i + 1, groupsize);
group[j].pop_back();
}
}
//新开一个组
group[groupsize].emplace_back(x);
dfs(i + 1, groupsize + 1);
group[groupsize].pop_back();
return;
}
int main() {
cin >> n >> m;
a.resize(n + 1);
for (int i = 0; i < n; ++i) cin >> a[i];
group.resize(n + 1);
sort(a.begin(), a.end());
reverse(a.begin(), a.end()); //从大到小枚举,优化搜索顺序
//放置原则
dfs(0, 0);
cout << res << endl;
return 0;
}
题目2:166数独
C++代码如下,
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 90;
char s[N];
int row[9]; //第i行可以填入哪些数,二进制表示第0位为1表示可以填入1,...,二进制表示第8位为1表示可以填入9。
int col[9]; //第j列可以填入哪些数
int box[3][3]; //第bi行第bj列的3*3的小方格可以填入哪些数
int map_val_idx[1 << 9]; //key是2^x,val是x。x = 0,...,8
int map_x_ones[1 << 9];
void init_st() {
int v = (1 << 9) - 1;
for (int i = 0; i < 9; ++i) row[i] = v;
for (int j = 0; j < 9; ++j) col[j] = v;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
box[i][j] = v;
}
}
return;
}
void change_st(int i, int j, int idx) {
s[i * 9 + j] = '1' + idx;
int v = 1 << idx; //第i行第j列填入数idx+1,二进制表示中第idx位设置为1
row[i] -= v;
col[j] -= v;
box[i / 3][j / 3] -= v;
return;
}
void restore_st(int i, int j, int idx) {
s[i * 9 + j] = '.';
int v = 1 << idx; //原先在第i行第j列填入数idx+1,现把它改为.
row[i] += v;
col[j] += v;
box[i / 3][j / 3] += v;
return;
}
int low_bit(int x) {
return x & -x;
}
bool dfs(int cnt) {
if (cnt == 0) {
return true;
}
//0~80,优先遍历选择少的
int ti = -1;
int tj = -1;
int minv = 10;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (s[i * 9 + j] == '.') {
int x = row[i] & col[j] & box[i / 3][j / 3];
int v = map_x_ones[x];
if (v < minv) {
minv = v;
ti = i;
tj = j;
}
}
}
}
//遍历(ti, tj),也就是说第ti行第tj列,填1-9中的哪一个数字
for (int x = row[ti] & col[tj] & box[ti / 3][tj / 3]; x > 0; x -= low_bit(x)) {
int y = low_bit(x);
int idx = map_val_idx[y];
change_st(ti, tj, idx);
if (dfs(cnt - 1)) return true;
restore_st(ti, tj, idx);
}
return false;
}
int main() {
for (int i = 0; i < 9; ++i) {
int x = 1 << i;
map_val_idx[x] = i;
}
for (int i = 0; i < 1 << 9; ++i) {
for (int j = 0; j < 9; ++j) {
map_x_ones[i] += i >> j & 1;
}
}
while (cin >> s, s[0] != 'e') {
init_st();
int cnt = 0;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (s[i * 9 + j] != '.') {
change_st(i, j, s[i * 9 + j] - '1');
} else {
cnt++;
}
}
}
dfs(cnt);
cout << s << endl;
}
return 0;
}
题目3:167木棒
C++代码如下,
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 70;
int n;
int w[N];
int sum, length;
bool st[N];
bool dfs(int u, int cur, int start) { //往第u组中,放入第start个数
//u表示第0组、第1组...
//cur表示当前组中元素之和
//start表示考虑第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] || cur + w[i] > length) continue;
st[i] = true;
if (dfs(u, cur + w[i], i + 1)) return true;
st[i] = false;
if (!cur || cur + w[i] == length) return false;
int j = i;
while (j < n && w[j] == w[i]) j++;
i = j - 1;
}
return false;
}
int main() {
while (cin >> n, n) {
memset(st, 0, sizeof st);
sum = 0;
for (int i = 0; i < n; ++i) {
cin >> w[i];
sum += w[i];
}
sort(w, w + n);
reverse(w, w + n);
length = w[0];
while (true) {
if (sum % length == 0 && dfs(0, 0, 0)) {
cout << length << endl;
break;
}
length++;
}
}
return 0;
}
题目4:168生日蛋糕
C++代码如下,
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 25, INF = 1e9;
int n, m;
int minv[N], mins[N];
int R[N], H[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;
R[u] = r, H[u] = h;
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
}
}
}
int main() {
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] = H[m + 1] = INF;
dfs(m, 0, 0);
if (ans == INF) ans = 0;
cout << ans << endl;
return 0;
}