深度优先搜索
回溯法模板
void dfs(int k) { // k代表递归层数,或者说要填第几个空
if (所有空已经填完了) {
判断最优解/记录答案;
return;
}
for (枚举这个空能填的选项)
if (这个选项是合法的) {
记录下这个空(保存现场);
dfs(k + 1);
取消这个空(恢复现场);
}
}
洛谷p1219 --- 经典八皇后问题
#include<iostream>
using namespace std;
const int N = 1e4;
int cnt;
int ans[N];
int n;//棋盘规模n * n
int row[N];//记录各行是否存在皇后
int col[N];//记录各列是否存在皇后
int dj1[N], dj2[N];//记录两条对角线是否存在皇后 dj1是从左上到右下 特点是每条对角线i - j 的值固定且不重复 由于存在负数 + n
// dj2是从左下到右上 特点是每个对角线 i + j 的值固定且不重复
void dfs(int x){//第x行皇后的放置
if(x > n){//表示皇后均已放置完毕
cnt ++;
if(cnt <= 3){//检查输出前三种放置方法
for(int i = 1; i <= n; i ++)
cout << ans[i] << ' ';
cout << endl;
}
return;
}
for(int i = 1; i <= n; i ++){
if(!row[x] && !col[i] && !dj1[x - i + n] && !dj2[x + i]){
//占位
row[x] = 1, col[i] = 1;
dj1[x - i + n] = 1, dj2[x + i] = 1;
//记录当前皇后列坐标
ans[x] = i;
//递归下一层
dfs(x + 1);
//归位
row[x] = 0, col[i] = 0;
dj1[x - i + n] = 0, dj2[x + i] = 0;
ans[x] = 0;//可写可不写 当前一组放置方法结束递归另一种时也会随即被覆盖 不会影响最终结果
}
}
}
int main(){
cin >> n;
dfs(1);
cout << cnt;
}
洛谷p2392 --- 枚举子集 深搜硬搜
对于每一科 由于相互独立 可以采用深搜来找到最适合左右脑处理的题目集合
//sub代表当前科目 x代表作业
void dfs(int x, int sub){//深度搜索单科所有作业 找到最合适的作业子集
if(x > len[sub]){
mintime = min(mintime, max(l, r));
return;
}
l += cnt[sub][x];//将该作业分配给左脑
dfs(x + 1, sub);
l -= cnt[sub][x];//归位
r += cnt[sub][x];//将该作业分配给右脑
dfs(x + 1, sub);
r -= cnt[sub][x];//归位
}
广度优先遍历
概念模板
Q.push(初始状态); // 将初始状态入队
while (!Q.empty()) {
State u = Q.front(); // 取出队首
Q.pop();//出队
for (枚举所有可扩展状态) // 找到u的所有可达状态v
if (是合法的) // v需要满足某些条件,如未访问过、未在队内等
Q.push(v); // 入队(同时可能需要维护某些必要信息)
}
洛谷p1443 --- 利用队列先进先出的性质 遍历起点及扩展点 类似树的层次遍历
#include<iostream>
#include<queue>
using namespace std;
const int N = 410;
int n, m, sx, sy;
int ans[N][N];//记录答案,-1表示未访问
int walk[8][2] = {{2, 1}, {1, 2}, {-1, 2}, {-2, 1},//代表马可以走的八个方位
{-2, -1}, {-1, -2}, {1, -2}, {2, -1}};
typedef struct Coord{
int x, y;
}coord;
queue<coord> Q;
void bfs(){
coord tem = {sx, sy};
Q.push(tem);//起点入队列
ans[sx][sy] = 0;//更新到达起点的步数
while(!Q.empty()){//循环扩展直到所有点都被扩展一遍
coord u = Q.front();//队首扩展
int ux = u.x, uy = u.y;
Q.pop();
int d = ans[ux][uy];//记录到当前点所走的步数
for(int i = 0; i < 8; i ++){
int x = ux + walk[i][0], y = uy + walk[i][1];
if(x >= 1 && x <= n && y >= 1 && y <= m && ans[x][y] == -1){//扩展点必须在棋盘内且未被访问
ans[x][y] = d + 1;
coord tep = {x, y};
Q.push(tep);
}
}
}
}
int main(){
cin >> n >> m >> sx >> sy;
for(int i = 1; i <= n; i ++)//初始化到达各点的步数 均设置为-1 表示未访问过
for(int j = 1; j <= m; j ++)
ans[i][j] = -1;
bfs();
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++)
printf("%d ", ans[i][j]);
printf("\n");
}
}
洛谷p1135 --- 电梯移动难题 跟上题思路完全一致
题目要求从电梯a到电梯b 从a开始入队列 出队列扩展 将扩展到的点入队列 当扩展到b点时结束 得到至少几次可以从a到达b
#include<iostream>
#include<queue>
using namespace std;
int n, a, b;
int m[210];//代表每层可直接移动的层数
int ans[210];//记录从a层到各层需要移动的次数
queue<int> Q;//存储扩展后的各层数
void bfs(){
Q.push(a);//起始a入队列
ans[a] = 0;
while(!Q.empty()){
int site = Q.front();
Q.pop();//队首出队
int up = site + m[site];//判断当前电梯可到达的上下层数
int down = site - m[site];
if(up > 0 && up <= n && ans[up] == -1){//若可以向上 未被访问 且并不超过最大电梯数 即扩展成功
Q.push(up);
ans[up] = ans[site] + 1;
}
if(down > 0 && down <= n && ans[down] == -1){//同理
Q.push(down);
ans[down] = ans[site] + 1;
}
if(up == b || down == b)//若已经移动到b层 即可结束扩展
break;
}
}
int main(){
cin >> n >> a >> b;
for(int i = 1; i <= n; i ++)
cin >> m[i];
for(int i = 1; i <= n; i ++)
ans[i] = -1;
bfs();
cout << ans[b];
}
介绍下万能头文件
若该文件和 using namespace std; 一同使用,则很多可用标识符(即变量/函数名称)已经被标准库使用,会导致编译错误。例如,全局变量的 y1 等。
void dfs(int startx, int sum, int count){//参数依次是 从哪个位置继续搜索 当前总和 相加了几个数
if(count == k){
if(check(sum))
res ++;
return ;
}
for(int i = startx; i < n; i ++)//深搜模拟枚举子集的代码
dfs(i + 1, sum + num[i], count + 1);
}
运行模拟 比如6选3
1 2 3 4 5 6
for循环的递归过程应为
1 2 3 检查是否满足条件
1 2 4
1 2 5
1 2 6
1 3 4
1 3 5
1 3 6
1 4 5
...
4 5 6
洛谷p2036 --- 典型的深搜枚举子集找最优解 但是我完全想不出来
最关键的就是想不到深搜的返回条件 思路比较简单 枚举各种配料的组合找到最小绝对差
关键在于dfs里的return要用什么条件去结束 题解给出的是用配料的组合数 从1~n依次调用dfs()每加入一种配料就-1 直到配料次数为0 返回上一层
//非常滴简单
void dfs(int count){//配料选取个数
if(count == 0){
res = min(res, abs(sum_s - sum_k));
return;
}
for(int i = 0; i < n; i ++){
count --;
book[i] = 0;
sum_s *= cnt[i].s;
sum_k += cnt[i].k;
dfs(count);
count ++;
book[i] = 1;
sum_s /= cnt[i].s;
sum_k -= cnt[i].k;
}
}
一点总结:针对深搜 基本应用就是给你一组数 去枚举各种组合 即子集 找到符合要求的最优解
模板大差不差 dfs内加入一个if返回 和for循环枚举元素 if的判断条件非常重要 应该是当前元素枚举结束的一个标志 比如上题的配料个数 n皇后问题的n 代表n个皇后已放置好
一点补充:比赛时输入输出scanf printf 快于cin cout
ios::sync_with_stdio(false) 直接在int main()内表明即可
在某些题目中,我们使用普通的cin和cout会超时,所以我们每次只能打scanf和printf,为什么cin和cout比scanf和printf用的时间多? 这是因为C++中,cin和cout要与stdio同步,中间会有一个缓冲,所以导致cin,cout语句输入输出缓慢,这时就可以用这个语句,取消cin,cout与stdio的同步,说白了就是提速,效率基本与scanf和printf一致。
memset(数组名,要初始化的数值,sizeof(数组名));比较方便地初始化数组的方法
头文件是include<cstring>
p1433吃奶酪问题 --- 看着能用dfs 算法标签打着DP 这怎么打
得60分 过了6个测试点 超时6个测试点 wa了一个测试点 可能目前数据加强暴搜过不了了 ---PPT是哪年编的呀!题解用dfs的方法都采用了dp的剪枝优化思路 暂时不打了
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int book[20];
int n;
double res = 1e9, length;
struct seat{
double x, y;
int flag;
}s[20];
double dis(double x1, double y1, double x2, double y2){//距离公式
return sqrt(pow((x1 - x2), 2) + pow((y1 - y2), 2));
}
void dfs(int count, int x, int y){
if(count == n){
res = min(res, length);
return;
}
for(int i = 0; i < n; i ++){
if(book[i] == 0){
length += dis(x, y, s[i].x, s[i].y);
if(length > res){//剪枝优化
length -= dis(x, y, s[i].x, s[i].y);
continue;
}
else{
count ++;//搬运奶酪
book[i] = 1;
dfs(count, s[i].x, s[i].y);
length -= dis(x, y, s[i].x, s[i].y);//归位
count --;
book[i] = 0;
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 0; i < n; i ++){
cin >> s[i].x >> s[i].y;
s[i].flag = 0;
}
dfs(0, 0, 0);
printf("%.2lf", res);
}
洛谷p1605 --- 走迷宫 经典的深搜 (我没见过但是一眼看出来用深搜的都是经典) 注意点就是记得标注起点的位置 因为每个点最多走一次 所以一开始起点就需要被标记 另外注意全局变量的命名 例如 存在move 就会编译失败 这题太简单了就不贴了