DFS剪枝策略总结
- 优化搜索顺序 优先搜索分支数少(剩余选择少)的情况
- 排除等效冗余 若对顺序没有要求 可以将排列转化为组合
- 可行性剪枝 不合法的情况不进行搜索
- 最优化剪枝 若当前的"消耗"已经超过暂存的答案 直接返回
- 记忆化搜索(DP)
AcWing 165.小猫爬山
翰翰和达达饲养了 N 只小猫 这天 小猫们要去爬山
经历了千辛万苦 小猫们终于爬上了山顶 但是疲倦的它们再也不想徒步走下山了
翰翰和达达只好花钱让它们坐索道下山
索道上的缆车最大承重量为 W 而 N 只小猫的重量分别是 C1、C2……CN
每辆缆车上的小猫的重量之和不能超过W
每租用一辆缆车 翰翰和达达就要付 美元 所以他们想知道
最少需要付多少美元才能把这N只小猫都运送下山?
输入格式
第 1 行:包含两个用空格隔开的整数 N 和 W
第 2~N+1 行:每行一个整数 其中第 i+1 行的整数表示第 i 只小猫的重量 Ci
输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。
数据范围
1≤N≤18
1≤Ci≤W≤10^8
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20;
int w[N], sum[N];//小猫重量和每辆车上当前的载重
int n, limit, ans;
//考虑到第u只猫 已经用了k辆车
void dfs(int u, int k)
{
//DFS剪枝优化(最优性剪纸)
//当前情况已经比在暂存答案更差了 就不用继续搜了
if (k >= ans)return;
//搜到了一个更优的方案 更新答案
if (u == n)ans = k;
//当前有k辆车 有k种放置选择
for (int i = 0; i < k; i++)
{
//DFS可行性剪枝
//必须满足条件才能放入
if (sum[i] + w[u] <= limit)
{
//放入猫猫
sum[i] += w[u];
dfs(u + 1, k);
//恢复现场
sum[i] -= w[u];
}
}
//新开一辆车也是一种选择
//k从0开始 sum[k]其实是指第k+1辆车
sum[k] = w[u];
dfs(u + 1, k + 1);
sum[k]=0;//恢复现场
}
int main()
{
cin >> n >> limit;
ans = n;
for (int i = 0; i < n; i++)cin >> w[i];
//DFS优化搜索顺序
//先搜索分支少的
//先放入重猫 这样剩下来的选择更少
sort(w, w + n);
reverse(w, w + n);
dfs(0, 0);
cout << ans << endl;
return 0;
}
AcWing 166. 数独
你需要把一个 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<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 9;
const int M = 1 << N;
//状态压缩 行 列 九宫格 用二进制数表示状态
int row[N], col[N], cell[3][3];
char str[N*N+10];//存图
//求二进制数中有多少个1
//求以2为底的对数
int ones[M], map[M];
//最开始什么数都没填入
//所有二进制位都为1 表示可以填
void init()
{
//二进制位全部设置位1
//二进制的第0位到第8位表示1-9
for (int i = 0; i < N; i++)row[i] = col[i] = (1 << N) - 1;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
cell[i][j] = (1 << N) - 1;
}
//在(x,y)填上t
//把(x,y)这个位置的数删掉
void draw(int x, int y, int t, bool is_set)
{
//is_set==true 表示填入
//二维坐标压缩到一维(注意要从0)开始
//t是0-8
//因为初始化的时候 二进制第0位设为1
if (is_set)str[x * N + y] = '1' + t;
else str[x * N + y] = '.';
//获取填入t时对应修改的二进制位
int v = 1 << t;
if (!is_set)v = -v;//删除时要减去
row[x] -= v;
col[y] -= v;
cell[x / 3][y / 3] -= v;
}
int lowbit(int x)
{
return x & (-x);
}
//分析(x,y)填入哪些数是合法的
int get(int x, int y)
{
return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int cnt)
{
//没有剩余的空格了 意味着填完了
if (!cnt)return true;
//寻找可以填的数(的情况)最少的的空格
//DFS的搜索顺序优化
//希望刚开始搜索时的分支数目尽可能少
int minv = 10;
int 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))
{
//lowbit(i)获取最低位的1
//i-=lowbit(i)相当于将最低位的1改为0
//lowbit(i)返回的是2的某次方 次方数对应填写的数字
int t = map[lowbit(i)];
draw(x, y, t, true);
if (dfs(cnt - 1))return true;
draw(x, y, t, false);//恢复现场
}
//DFS函数最后通常会有一个return false
//当该层的搜索循环迭代完都没有return true的机会
//说明该层的所有情况都不行了
//应当返回到上一层 撤销这次错误的尝试(恢复现场)
//return true 一般在开头
return false;
}
int main()
{
//求以2为底的对数
for (int i = 0; i < N; i++)map[1 << i] = i;
//求二进制数中有多少个1
for (int i = 0; i < 1 << N; i++)
for (int j = 0; j < N; j++)
ones[i] += (i >> j) & 1;
while (cin >> str, str[0] != 'e')
{
init();
//cnt表示有多少个空位
int cnt = 0;
//把一行字符串映射到9X9的二维数组
for (int i = 0, k = 0; i < N; i++)
for (int j = 0; j < N; j++, k++)
if (str[k] != '.')
{
//t是0-8
//因为初始化的时候 二进制第0位设为1
int t = str[k] - '1';
//将字符覆盖为数字
draw(i, j, t, true);
}
else cnt++;
dfs(cnt);
puts(str);
}
return 0;
}