蓝桥杯备考---搜索(深搜与广搜)

深度优先搜索

回溯法模板
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];
}

介绍下万能头文件

#include<bits/stdc++.h>

若该文件和 using namespace std; 一同使用,则很多可用标识符(即变量/函数名称)已经被标准库使用,会导致编译错误。例如,全局变量的 y1 等。
       
        洛谷p1036 --- 难点在于从一堆数中枚举出n个数来  利用深搜枚举子集 之后判断该子集是否满足条件即可
核心代码
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 就会编译失败  这题太简单了就不贴了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值