目录
关于回溯:先dfs再改变当前状态不需要恢复现场,先改变当前状态再dfs则需要恢复现场
换一种解释:
- 一种将DFS过程看成是在棋盘体内部进行搜索,从一个点搜索到另一个点,为了保证每个点仅能搜索一次,是不能恢复现场的;
- 一种将DFS过程看成是把棋盘看成一个整体,从一个状态搜索到另一个状态,那么就需要先恢复状态才能继续搜索下一个状态。
代码模板
const int N = 100010, M = 2 * N;
int h[N], e[M], ne[M], idx;
bool st[N]; // 记录每个结点是否被搜索过
// 从第u个结点开始搜索,通常u取1
void dfs(int u) {
st[u] = true; // 标记被搜索
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i]; // 保存当前结点对应图里面的编号
if (!st[j]) dfs(j);
}
}
1、连通性模型
DFS 只能求出是否连通,但是不能求最短路。
1.1 迷宫
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int n, x1, y1, x2, y2;
char g[N][N];
bool st[N][N];
int dx[] = {
0, -1, 0, 1}, dy[] = {
-1, 0, 1, 0};
bool dfs(int x, int y) {
if (g[x][y] == '#') return false; // 这个放到for里面会漏掉初始或者结束为#的情况,并且必须放在 dfs 第一行
if (x == x2 && y == y2) return true;
st[x][y] = true;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= n) continue;
if (st[a][b]) continue;
if (dfs(a, b)) return true;
}
return false;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%s", g[i]);
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
memset(st, 0, sizeof st);
if (dfs(x1, y1)) puts("YES"); else puts("NO");
}
return 0;
}
1.2 红与黑
注意:本题先输入列数,后输入行数
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 25;
int n, m;
char g[N][N];
bool st[N][N];
int dx[] = {
0, -1, 0, 1}, dy[] = {
-1, 0, 1, 0};
inline int dfs(int x, int y) {
int cnt = 1;
st[x][y] = true;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (g[a][b] != '.' || st[a][b]) continue;
cnt += dfs(a, b);
}
return cnt;
}
int main() {
while (scanf("%d%d", &m, &n), m || n) {
memset(st, 0, sizeof st);
for (int i = 0; i < n; i++) scanf("%s", g[i]);
int x, y;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (g[i][j] == '@') {
x = i, y = j; break;
}
printf("%d\n", dfs(x, y));
}
return 0;
}
1.3 通信网络
开两个布尔数组 s t 1 、 s t 2 st1、st2 st1、st2,分别表示能够从当前点能够走到的所有点以及能走到当前点的点(从该点开始沿着反向边遍历即可)。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010, M = 20010;
int n, m;
int h1[N], h2[N], e[M], ne[M], idx;
bool st1[N], st2[N];
void add(int h[], int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int h[], bool st[]) {
st[u] = true;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) dfs(j, h, st);
}
}
int main() {
scanf("%d%d", &n, &m);
memset(h1, -1, sizeof h1); memset(h2, -1, sizeof h2);
while (m--) {
int a, b; scanf("%d%d", &a, &b);
add(h1, a, b), add(h2, b, a);
}
int res = 0;
for (int i = 1; i <= n; i++) {
memset(st1, 0, sizeof st1); memset(st2, 0, sizeof st2);
dfs(i, h1, st1); dfs(i, h2, st2);
int s = 0;
for (int j = 1; j <= n; j++)
if (st1[j] || st2[j]) s++;
if (s == n) res++;
}
printf("%d\n", res);
return 0;
}
1.4 树的重心
Tips:dfs 可以计算出每个结点子树的大小
注意:
- s u m sum sum:记录当前子树的大小,这里根节点算一个
- r e s res res:表示将这个点删除之后每个连通块结点数的最大值
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int n;
int h[N], e[M], ne[M], idx;
bool st[N];
int ans = N;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 返回以u为根结点的树的子树结点个数
int dfs(int u) {
st[u] = true;
int sum = 1, res = 0;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) {
int s = dfs(j); // 表示当前子树的大小
res = max(res, s); // 可以求出根结点的子树的连通块中结点数最大值
sum += s;
}
}
res = max(res, n - sum); // 除去结点u及其子树剩余结点构成的一个连通块中的数量
ans = min(ans, res);
return sum;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d", &n);
for (int i = 0; i < n - 1; i++) {
int a, b; scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs(1);
printf("%d\n", ans);
return 0;
}
1.5 网络延时(树的直径)
求将每个点作为最高结点的时候,它到其所有儿子结点的最长距离和次长距离,该值就是以该节点为最高结点的最长路径。
时间复杂度: O ( n ) O(n) O(n)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 20010;
int n, m;
int h[N], e[N], ne[N], idx;
int ans;
inline void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
inline int dfs(int u) {
int d0 = 0, d1 = 0; // 当前结点下的最大长度和次大长度
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
int d = dfs(j);
if (d > d0) d1 = d0, d0 = d;
else if (d > d1) d1 = d;
}
ans = max(ans, d0 + d1);
return d0 + 1; // +1 表示当前结点往父结点走的边
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 2; i <= n + m; i++) {
int p; scanf("%d", &p);
add(p, i);
}
dfs(1);
printf("%d\n", ans);
return 0;
}
1.6 I’m stuck!
两次DFS,第一次DFS找到所有能够从起点走到的点,用数组 s t 1 st1 st1记录,第二次DFS找到所有能够从终点走到的点,用数组 s t 2 st2 st2记录。最终判断点 ( i , j ) (i,j) (i,j)是否满足条件 s t 1 [ i ] [ j ] = t r u e & & s t 2 [ i ] [ j ] = f a l s e st1[i][j] = true\ \&\&\ st2[i][j] = false st1[i][j]=true && st2[i][j]=false即可。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 55;
int n, m;
char g[N][N];
bool st1[N][N], st2[N][N];
int dx[] = {
-1, 0, 1, 0,}, dy[] = {
0, 1, 0, -1};
inline bool check(int x, int y, int k) {
char c = g[x][y];
if (c == '+' || c == 'S' || c == 'T') return true;
if (c == '|' && (k == 0 || k == 2)) return true;
if (c == '-' && (k == 1 || k == 3)) return true;
if (c == '.' && k == 2) return true;
return false;
}
inline void dfs1(int x, int y) {
st1[x][y] = true;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st1[a][b] || g[a][b] == '#') continue;
if (check(x, y, i)) dfs1(a, b);
}
}
inline void dfs2(int x, int y) {
st2[x][y] = true;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st2[a][b] || g[a][b] == '#') continue;
if (check(a, b, i ^ 2)) dfs2(a, b);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%s", g[i]);
int x, y;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (g[i][j] == 'S') dfs1(i, j);
else if (g[i][j] == 'T') {
x = i, y = j; dfs2(i, j);
}
if (!st1[x][y]) puts("I'm stuck!");
else {
int res = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (st1[i][j] && !st2[i][j]) res++;
printf("%d\n", res);
}
return 0;
}
2、搜索顺序模型
2.1 排列数字
对比第一章 10.1 ACwing 92. 递归实现指数型枚举
遍历过程: 如下图,注意,dfs(0)
对应图中第一层,dfs(1)
对应图中第二层,依次类推。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10;
int n, path[N];
bool st[N];
void dfs(int u) {
if (u == n) {
for (int i = 0