DFS和BFS基础知识大家可以看这个博主,讲的很好:第十三章 DFS与BFS(保姆级教学!!超级详细的图示!!)_dfs bfs-优快云博客
我的总结就是DFS剪枝,BFS判重
下面的题目难度不分先后
DFS
T1:买瓜(23年蓝桥杯A组省赛)
解题思路:DFS+两种剪枝
首先对于一个瓜,我们可以要整个,要一半(即劈瓜),或者不要,三个选择,我们可以用这三种情况来做DFS,题目最多30个瓜,一个瓜三种状态,时间复杂度足足有O(3e10),剪枝!!!
剪枝策略:
- 最优解剪枝:当前劈瓜数已经大于了目前的最优解,回溯
- 合法解剪枝:后面的瓜全要也无法达到要求的总重m/当前总重大于m,回溯
其中后面的瓜全要需要计算后缀和,前/后缀和剪枝也是DFS中很常用的;
最后注意特判-1的情况
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
int n,m;
int a[35];
int sum[35]; // 统计后缀和
int ans = 35; // 答案
void dfs(int cnt, int s, int i) {
if (cnt >= ans) return ; // 当前次数大于目前的最优解
if (s == m) ans = min(ans, cnt); // 合法答案
if (s + sum[i] < m || s >= m || i > n) return ; // 不可行答案:后面的瓜全要也无法达到m/当前已经大于m
// 1.要整个瓜
dfs(cnt, s + a[i], i + 1);
// 2.劈一半
dfs(cnt + 1, s + a[i]/2, i + 1);
// 3.不要这个瓜
dfs(cnt, s, i + 1);
}
void solve() {
cin >> n >> m; m*=2;
for (int i = 1;i <= n; i++) cin >> a[i], a[i]*=2;
sort(a + 1,a + n + 1, greater<int>());
for (int i = n;i >= 1; i--) {
sum[i] = sum[i + 1] + a[i];
}
dfs(0,0,1); // 已经劈的瓜数/当前总重/当前处理的瓜
if (ans == 35) cout << "-1";
else cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T2:路径之谜(16年蓝桥杯A组国赛)
解题思路:DFS
DFS搜索从起点到终点的每一条路径,如果终点时靶子刚好打完,就是合法答案,这道题的特点是回溯属性较多,判重/记录路径/打靶,使用vector来存路径是因为删除很方便
st[nx][ny] = 1; path.push_back((nx-1)*n+ny-1); north[ny]--,west[nx]--; // 靶数减少 dfs(nx, ny); north[ny]++,west[nx]++; // 回溯 path.pop_back(); st[nx][ny] = 0;
此时你自信满满的写好了代码,就会发现超时了!!!,剪枝!!!
当还未到达终点但是靶子已经打完时,回溯,加上这一句代码之后,通过!!!
if (north[y] < 0 || west[x] < 0) return ; // 剪枝
所以在你写DFS的时候,要多想想哪里能剪枝,能不能继续剪枝,一行剪枝就能多对5个测试用例!!!
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
int n;
int north[25], west[25]; // 靶子
int g[25][25]; // 存图
int st[25][25]; // 标记
vector<int> path; // 存路径
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};
// 判断靶子是否打完,即是否为正确路径
bool check() {
for (int i = 1;i <= n; i++) {
if (north[i] || west[i]) return false;
}
return true;
}
void dfs(int x, int y) { // 当前点
if (x == n && y == n) { // 到达出口
if (check()) { // 为正确路径
for (auto c:path) cout << c << " ";
return ;
}
}
if (north[y] < 0 || west[x] < 0) return ; // 剪枝
for (int i = 1;i <= 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx<=0||nx>n||ny<=0||ny>n||st[nx][ny]) continue;
st[nx][ny] = 1;
path.push_back((nx-1)*n+ny-1);
north[ny]--,west[nx]--; // 靶数减少
dfs(nx, ny);
north[ny]++,west[nx]++; // 回溯
path.pop_back();
st[nx][ny] = 0;
}
}
void solve() {
cin >> n;
for (int i = 1;i <= n; i++) cin >> north[i];
for (int i = 1;i <= n; i++) cin >> west[i];
// 注意对起点处理
st[1][1] = 1;
north[1]--,west[1]--;
path.push_back(g[1][1]);
dfs(1,1);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T3:跳跃
解题思路
虽然该题的正解是dp,但是可以很好的练习搜索,该题的特点是它搜索的方向向量,不是上下左右,而是右下,并且走的步数是1,2,3
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
int n,m;
int a[105][105],ans = -1e9;
int ds[10][3] = {{0,1},{0,2},{0,3},
{1,0},{2,0},{3,0},
{1,1},{2,2},{3,3}};
void dfs(int x,int y,int sum) {
if (x == n && y == m) {
ans = max(ans,sum);
return ;
}
for (int j = 0;j < 9; j++) {
int nx = x + ds[j][0];
int ny = y + ds[j][1];
if (nx<=0||nx>n||ny<=0||ny>m) continue;
dfs(nx,ny,sum+a[nx][ny]);
}
}
void solve() {
cin >> n >> m;
for (int i = 1;i <= n; i++) {
for (int j = 1;j <= m; j++) {
cin >> a[i][j];
}
}
dfs(1,1,a[1][1]);
cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T4:穿越雷区(16年蓝桥杯国赛)
解题思路
特点是要交替走网格,就可以以此来剪枝,虽然是国赛,但是很简单,注意起点和终点的状态
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
using pii = pair<int,int>;
int n;
pii a,b; // A/B
char g[105][105]; // 存图
int st[105][105];
int ans = 1e9;
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};
// 当前点,步数,以及上一个点的状态
void dfs(int x, int y, int cnt, char status) {
if (x == b.f && y == b.s) { // 合法答案
ans = min(ans, cnt);
return ;
}
for (int i = 1;i <= 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx<=0||nx>n||ny<=0||ny>n||st[nx][ny]) continue;
if (g[x][y]!='A' && status == g[nx][ny]) continue;
st[nx][ny] = 1;
dfs(nx,ny,cnt+1,g[nx][ny]);
st[nx][ny] = 0;
}
}
void solve() {
cin >> n;
for (int i = 1;i <= n; i++) {
for (int j = 1;j <= n; j++) {
cin >> g[i][j];
if (g[i][j] == 'A') a = {i,j};
if (g[i][j] == 'B') b = {i,j};
}
}
dfs(a.f, a.s, 0, 1);
ans = (ans == 1e9?-1:ans);
cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T5: 飞机降落(23年蓝桥杯省赛)
解题思路:
DFS飞机降落的顺序
剪枝策略:当有飞机无法降落时回溯
代码:
// DFS
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
struct node {
int t; // 最早降落时间
int d; // 周旋时间
int l; // 降落花费的时间
}a[20];
int n;
string ans;
bool st[N]; // 标记当前飞机是否降落
// 已经降落了几架飞机以及上一架飞机降落的时间
void dfs(int u,int last) {
if (u >= n) { // 飞机全部安全降落,递归退出点
ans = "YES";
return ;
}
for (int i = 1;i <= n; i++) {
if (st[i]) continue;
// 1.判断当前飞机是否可以降落
if (a[i].t + a[i].d < last) return ; // 不能降落,回溯
// 2.思考u+1个位置由谁降落
st[i] = true;
dfs(u + 1,max(a[i].t,last) + a[i].l);
st[i] = false;
}
}
void solve() {
cin >> n; // 飞机架数
for (int i = 1;i <= n; i++) st[i] = false; // 初始化st数组
for (int i = 1;i <= n; i++) {
cin >> a[i].t >> a[i].d >> a[i].l;
}
ans = "NO";
dfs(0,0);
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t; cin >> t;
while (t--) {
solve();
}
return 0;
}
T6:卡片换位(16年蓝桥杯省赛)
解题思路:DFS
交换可以看成移动空格,手动模拟一下就会发现,最多17就一定能实现交换,我们枚举交换的次数0~17,然后对于每一个答案做dfs,能满足条件的第一个答案即为最小值;
但是DFS不是正解,正解是BFS
代码
// DFS遍历
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
using pii = pair<int,int>;
const int N = 5;
char g[N][N];
pii a,b,k;
int sum = 0;
int num = -1;
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};
void dfs(int x,int y,int cnt) {
if (g[a.f][a.s] == 'B' && g[b.f][b.s] == 'A') { // 目标状态
num = cnt;
return ;
}
if (cnt > sum) return ;
for (int i = 1;i <= 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx<0||nx>=2||ny<0||ny>=3) continue;
swap(g[x][y], g[nx][ny]); // 交换
dfs(nx, ny, cnt+1);
swap(g[x][y], g[nx][ny]); // 回溯
}
}
void solve() {
for (int i = 0;i < 2; i++) {
string s1; getline(cin, s1);
for (int j = 0;j < 3; j++) g[i][j] = s1[j];
}
for (int i = 0;i < 2; i++) {
for (int j = 0;j < 3; j++) {
if (g[i][j] == 'A') a = {i,j}; // A的位置
if (g[i][j] == 'B') b = {i,j}; // B的位置
if (g[i][j] == ' ') k = {i,j}; // 空格的位置
}
}
for (int i = 1;i <= 20; i++) { // 枚举次数
sum = i;
dfs(k.f,k.s,0);
if (num != -1) {
cout << num;
return ;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
BFS
写了好多BFS的题,最重要的一点就是判重,写的好几个题都是通过二维转一维,即将图转化成字符串来保存状态,使用map/set来标记判重
T1:青蛙跳杯子
解题思路
我们用BFS遍历青蛙跳杯子的所有状态,定义map<string,int> 来标记每个状态,去掉重复的状态,当到达目标状态时的步数即为答案;
注意:每次遍历都有一个找空杯子的过程,我们可以用string的find函数来快速找到空杯子的位置;
代码
// BFS搜索,map去重,注意状态要还原
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
string s,e;
int n;
unordered_map<string, int> ump;
int dx[] = {0,-3,-2,-1,1,2,3};// 向左右跳的6种情况
queue<string> q;
void bfs() {
// 初始化
q.push(s);
ump[s] = 0;
while (q.size()) {
string now = q.front(); q.pop(); // 当前串
int cnt = ump[now]; // 当前次数
if (now == e) { // 合法答案
cout << cnt;
return ;
}
int k = now.find('*'); // 空杯子的位置
for (int i = 1;i <= 6; i++) { // 枚举6个位置
int nk = k + dx[i];
if (nk < 0 || nk >= n) continue;
swap(now[nk], now[k]); // 移动
if (!ump[now]) {
ump[now] = cnt + 1;
q.push(now); // 当前是一个新状态
}
swap(now[nk], now[k]); // 还原
}
}
}
void solve() {
cin >> s;
cin >> e;
n = s.size();
bfs();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T2:卡片换位(16年蓝桥杯省赛)
解题思路:BFS
该题可以说是上一题的二维版本,但是我们任然使用一维字符串来存状态,就需要一维和二维的转换;
- 二维转一维:直接将每一行拼接即可
- 一维转二维:除法和取模
BFS搜索空格的交换方案,并用字符串记录当前方案的状态,遇到的第一个合法答案就是最小步数(因为BFS有最短路性质),map即标记了当前状态,又保存当前状态与步数之间的关系;
注意:
- 使用unordered_map比map效率更高
- 注意要使用getline读入带空格的字符串
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};
unordered_map<string, int> ump; // 存状态与步数的关系
queue<string> q;
string s;
int a,b; // 存A/B的下标
void bfs() {
q.push(s);
ump[s] = 0;
while (q.size()) {
auto now = q.front(); q.pop(); // 当前状态
int cnt = ump[now]; // 当前步数
if ((int)now.find('A')==b && (int)now.find('B')==a) { // 交换成功
cout << cnt;
return ;
}
int pos = now.find(" "); // 找空格的位置
int x = pos/3, y = pos%3; // 转二维
for (int i = 1;i <= 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx<0||nx>=2||ny<0||ny>=3) continue;
swap(now[pos], now[nx*3+ny]); // 交换
if (!ump[now]) { // 当前是新状态
ump[now] = cnt + 1; // 标记
q.push(now);
}
swap(now[pos], now[nx*3+ny]); // 还原
}
}
}
void solve() {
for (int i = 1;i <= 2; i++) {
string t;
getline(cin, t); // 带空格的字符串的读入方式
s += t;
}
a = s.find('A');
b = s.find('B');
bfs();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
T3:九宫重排(13年蓝桥杯国赛)
解题思路
该题是卡片换位的升级版,由2*3变成了3*3,思路几乎一摸一样,不做讲解
代码
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
const int N = 1e5 + 50;
string s,e;
unordered_map<string, int> ump;
queue<string> q;
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};
int bfs(string s, int cnt) {
// 初始化
q.push(s);
ump[s] = cnt;
while (q.size()) {
string now = q.front(); q.pop(); // 当前状态
int cnt = ump[now]; // 当前步数
if (now == e) { // 目标状态
return cnt;
}
int pos = now.find('.'); // 空格的位置
int x = pos/3, y = pos%3; // 一维转二维
for (int i = 1;i <= 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3) continue;
swap(now[pos], now[nx*3 + ny]); // 交换
if (!ump[now]) { // 为处理过的状态
ump[now] = cnt + 1;
q.push(now);
}
swap(now[pos], now[nx*3 + ny]); // 交换 // 还原
}
}
return -1;
}
void solve() {
cin >> s >> e;
int ans = bfs(s,0); // 当前字符串和次数
cout << ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}