【洛谷】DFS 新手必学的4 道DFS经典题 手把手教你剪枝与回溯

文章目录


选数

题目描述

题目解析

本题的搜索思路和上一章介绍的组合型枚举基本相同,无非就是多了一个判断是否是素数,我们采用试除法,将目标数字num分别与2-根号下num相除,如果所有数都除不尽则为素数。

另外因为本题不用输出选择的数,只用判断决策树路径数之和是否为素数,所以本题的全局变量不用拿vector数组存储,用一个整型变量path表示当前路径数之和即可,每一个dfs前后做出选择和恢复现场只用对path做操作。

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

const int N = 25;

int n, k;
int a[N]; //存储n个整数
int path; //记录路径中的数之和
int ret; //最终结果

bool isprime(int x)
{
	//素数从2开始判断,1既不是素数也不是合数
	if (x <= 1)
		return false;
	for (int i = 2; i <= sqrt(x); i++)
	{
		if (x % i == 0)
		{
			return false;
		}
	}
	return true;
}

void dfs(int pos, int begin)
{
	if (pos > k)
	{
		if(isprime(path)) 
			ret++;
		return;
	}

	for (int i = begin; i <= n; i++)
	{
		path += a[i];
		dfs(pos + 1, i + 1); //当前填的第i个,从i + 1开始继续往后填
		path -= a[i];
	}
}

int main()
{
	//处理输入
	cin >> n >> k;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	
	dfs(1, 1);
	cout << ret << endl;
	return 0;
}

飞机降落

题目描述

题目解析

本题我们可能会优先想到用贪心的区间问题解法解决,但是因为本题的数据量比较小:N <= 10,所以我们可以考虑枚举所有情况解决,但是因为本题的N不确定,所以我们不能用for循环枚举,因为无法确定枚举层数,所以需要用搜索枚举。

本题的枚举策略是先枚举N个数的全排列,之前我们介绍过。

本题的dfs除了需要传递pos位置,还需要传递上一架飞机的降落结束时间,那么也就是需要算出当前飞机的降落结束时间,飞机的降落结束时间有两种情况,一种是到达机场直接可以降落,降落结束时间为t[i] + l[i],另一种是飞机到来时上架飞机还在占用跑道,需要等待上架飞机降落完毕当前飞机才可以开始降落,降落结束时间为end + l[i],所以本质就是end和t[i]取大再加上l[i]。(补充,计算当前飞机的降落结束时间时不用考虑飞机油量是否足够(是否满足t[i] + d[i] >= end),因为在计算之前若油量不够当前分支已经被剪掉了,如果能走到计算飞机的降落结束时间这里说明油量一定足够)
在枚举过程中需要进行剪枝,本题有三处需要剪枝:

1、因为枚举全排列,所以需要一个st数组存储选择过的飞机编号,若选择过则直接continue,递归下一种情况。

2、本题的dfs除了需要传递pos位置,还需要传递上一架飞机的降落结束时间,因为当前飞机需要判断比较飞机最晚起降时间(t[i] + d[i] )和上一架飞机的降落结束时间(end),如果飞机最晚起降时间小于上一架飞机的降落结束,说明当前飞机的油量不足以支撑到上一架飞机的降落结束,需要将该分支剪掉,也就是continue。

3、当枚举过程中dfs已经返回true了,说明已经有情况分支满足条件了,这时不需要继续向后枚举了,直接返回true即可。
注意:

1、本题我们要通过dfs判断该组数据是否能完成降落任务,所以需要一个bool类型的返回值。

2、多组测试用例需要重置数据,本题只用重置st数组,因为其他数组读入新数据时老数据会被覆盖掉。

代码

cpp 复制代码
#include <iostream>
#include <string.h>
using namespace std;

const int N = 1e5 + 10;
const int M = 15;

int T, n;
int t[N], d[N], l[N];
bool st[M]; //标记当前路径哪些飞机选择过

//pos为当前选择第几架飞机, end为上一架飞机的降落结束时间
bool dfs(int pos, int end)
{
	if (pos > n)
	{
		//排列完毕并且合法,返回true
		return true;
	}

	for (int i = 1; i <= n; i++)
	{
		//剪枝1
		if (st[i])
			continue;
		//剪枝2,若当前飞机最晚起降时间小于上一架飞机的结束时间
		// 说明当前飞机无法降落,因为油量无法支持
		if (t[i] + d[i] < end)
			return false;

		st[i] = true;
		//最优情况当前飞机从t[i]时刻开始下降,但若上一架飞机
		//还在占用跑道需要等待它降落再开始下降,故用max
		int newend = max(t[i], end) + l[i];
		//剪枝3,假设若1__分枝已经有情况枚举成功,
		// 不用再向后枚举了,直接返回true
		if(dfs(pos + 1, newend)) return true;
		st[i] = false;
	}
	//走到这里表示for循环枚举的所有情况没有一个符合条件
	// (因为符合条件的已经return true了)
	return false;
}

int main()
{
	cin >> T;
	//T组数据
	while (T--)
	{
		//多组测试用例需重置数据
		memset(st, 0, sizeof(st));
		cin >> n;
		for (int i = 1; i <= n; i++)
		{
			//到达时间,可以盘旋时间,降落花费时间
			cin >> t[i] >> d[i] >> l[i];
		}

		//刚开始结束时间end从0开始
		if (dfs(1, 0))
			cout << "YES" << endl;
		else
			cout << "NO" << endl;
	}
	return 0;
}

八皇后

题目描述

题目解析

八皇后的原题背景是国际象棋,国际象棋中的皇后棋子的行动范围是一行、一列、主对角线和副对角线,如下图所示,本题题意就是在棋盘上摆放皇后旗,让任意两个皇后棋不会互相打起来。

本题利用暴搜解决,思路是每一行放置一个棋子,第一行随机挑选一个位置放置,然后依据第一行棋子的位置,判断第二行棋子可以摆放的位置,本题的难点是剪枝策略,也就是如何判断两颗棋子是否会打起来。

首先行我们不用判断,因为我们就是按行的顺序摆放的。 然后判断列,我们用一个bool数组col标记哪些列摆放过。

最后是判断主对角线和副对角线,这里我们利用一次函数进行标记,我们知道主对角线的函数为y = x + b, 副对角线函数为y = -x + c,根据一次函数的性质每一个主对角线都有一个唯一的b,每一个副对角线都有一个唯一的c,我们利用两个st数组:st1标记哪些主对角线摆放过和st2标记哪些副对角线摆放过,当填入一个棋子时,我们把棋子的下标(x, y)转化为 y - x,和y + x,将 对应的对应的st数组置为true,但是这里需要注意两个点:

1、y - x是可能出现负值的,因为y - x对应的是数组下标,所以y - x需要加上一个大于等于n的偏移量,将 y - x 的值映射为非负。

2、因为主对角线和副对角线的情况比较多,如下图所示,所以开st1和st2数组空间时需要多开一倍。

代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int N = 14;

int n;
int ret; //解的总数
vector<int> path; //存储每种解

bool col[N]; //标记哪些列放过
bool st1[2 * N]; //标记主对角线放过
bool st2[2 * N]; //标记哪些副对角线放过

void dfs(int x)
{
	if (x > n)
	{
		ret++;
		if (ret <= 3)
		{
			for (auto& e : path)
			{
				cout << e << " ";
			}
			cout << endl;
		}
		return;
	}

	for (int y = 1; y <= n; y++)
	{
		if (col[y] || st1[y - x + n] || st2[y + x])
			continue;
		
		col[y] = st1[y - x + n] = st2[y + x] = true;
		path.push_back(y);
		dfs(x + 1);
		col[y] = st1[y - x + n] = st2[y + x] = false;
		path.pop_back();
	}
}

int main()
{
	cin >> n;
	dfs(1);
	cout << ret << endl;
	return 0;
}

数独

题目描述

题目解析

本题题意是在一个9 * 9二维数组中,每一行、每一列、每一个紫色方框都只能有1-9这9个数字,不能有重复的数字。

思路依旧用暴搜解决,一个格子一个格子的填数,每个格子从1-9依次尝试,找到一个合法的数填入。

剪枝策略如下:

1、用三个bool类型的数组分别标记某行、某列、某个粗线宫有1-9的哪些数字,如果标记为true,则在dfs枚举1-9数字中直接continue,枚举下一个数字,在处理输入时往格子填数时,若遇到不为0的格子需要将当前格子对应的三个bool类型数组置为true。
注意:

1、本题的粗线宫一共有9个,3 * 3,我们在进行标记时需要找到当前格子对应哪个粗线宫,所以需要将数独棋盘的行、列下标设置为0-8,这样不管是行下标还是列下标,只有除以3就就可以得到粗线宫的下标0-2。

2、本题只要找到一种合法情况就可以递归返回了,所以dfs需要一共bool返回值。

代码

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 10;
const int M = 3;

int n = 9;
int a[N][N]; //存储原数独
bool row[N][N]; //行 数 标记某行有1-9的哪些数
bool col[N][N]; //列 数 标记某列有1-9的哪些数
bool st[M][M][N]; //3 * 3 * 10 标记某粗线宫有1-9的哪些数

bool dfs(int x, int y)
{
	if (y == n)
	{
		//枚举到了最后一列的下一列,换下一行
		x++;
		y = 0;
	}

	if (x == n)
	{
		//枚举到了最后一行,找到了一种合法情况,停止递归,返回true
		return true;
	}


	if (a[x][y])
	{
		//当前格(z, y)已经有数了,枚举下一格
		return dfs(x, y + 1);
	}

	for (int i = 1; i <= 9; i++)
	{
		if (row[x][i] || col[y][i] || st[x / 3][y / 3][i])
			continue;

		row[x][i] = col[y][i] = st[x / 3][y / 3][i] = true;
		a[x][y] = i;
		if (dfs(x, y + 1))
			return true;
		row[x][i] = col[y][i] = st[x / 3][y / 3][i] = false;
		a[x][y] = 0;
	}
	//走到这里说明这个格子填1-9都不合法,说明前面有某一层的格子填的不合法,需要继续枚举
	return false;
}

int main()
{
	//初始化
	int x = 0;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cin >> a[i][j];
			x = a[i][j];
			if (x)
			{
				//x不为零
				row[i][x] = col[j][x] = st[i / 3][j / 3][x] = true;
			}
		}
	}

	dfs(0, 0);

	//输出结果
	for (int i = 0; i <= 8; i++)
	{
		for (int j = 0; j <= 8; j++)
		{
			cout << a[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的赞和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

相关推荐
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章15-多边形逼近
图像处理·人工智能·opencv·算法·计算机视觉
一条大祥脚2 小时前
势能分析与势能线段树
开发语言·javascript·数据结构·算法
想做一只开心的菜鸡2 小时前
DARTS#01 | Tournament Sort算法 | MySQL深度翻页优化技巧 | 论文ByteSlice Review
数据库·mysql·算法
一起养小猫2 小时前
Flutter实战:从零实现俄罗斯方块(一)数据结构与核心算法
数据结构·算法·flutter
2501_901147832 小时前
四数相加问题的算法优化与工程实现笔记
笔记·算法·面试·职场和发展·哈希算法
亿秒签到2 小时前
第六届传智杯程序设计国赛B组T4·小苯的字符串染色
数据结构·算法·传智杯
chao1898442 小时前
基于字典缩放的属性散射中心参数提取算法与MATLAB实现
开发语言·算法·matlab
石去皿2 小时前
大厂AI算法面试题汇总
人工智能·算法
晚风吹长发2 小时前
初步了解Linux中的信号保存和简单使用
linux·运维·服务器·数据结构·后端·算法