搜索
DFS
1.全排列模型
题目描述
- 场景 :你有 NNN 个空盒子排成一排(编号 1 到 NNN),手里有 NNN 张卡片(数字 1 到 NNN)。
- 规则:每个盒子只能放 1 张卡片。
- 目标:走到最后一个盒子并放好卡片后,你就得到了一种"全排列"。我们要找出所有的放法。
递归函数dfs的逻辑:
- 你站在第
step个盒子面前。 - 你看一下手里还有哪些卡片没被用掉(遍历
1到N,检查vis数组)。 - 尝试 :你挑一张没用的卡片(比如
i),把它放进当前的盒子。 - 标记 :你在心里记下"卡片
i没了"(vis[i] = true)。 - 递归 :你走到下一个盒子面前(
dfs(step + 1))。 - 回溯(关键!) :等你从下一个盒子退回来时,你必须把刚才放进去的卡片 i 拿出来 ,放回手里(
vis[i] = false),这样你才能尝试下一张卡片。
代码模版
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10; // 蓝桥杯一般 N <= 10,因为 10! = 362万
int n;
int path[MAXN]; // 盒子:path[i] 表示第 i 个盒子里放的数字
bool vis[MAXN]; // 标记:vis[i] == true 表示数字 i 已经被用过了
// step 表示当前正在填第 step 个盒子
void dfs(int step) {
// 【1. 截止条件(撞南墙)】
// 如果我们要填第 n+1 个盒子,说明前 n 个都已经填满了
if (step == n + 1) {
// 输出这一个解
for (int i = 1; i <= n; i++) {
cout << path[i] << " ";
}
cout << endl;
return; // 返回上一层
}
// 【2. 遍历候选人】
// 在当前这个盒子,尝试放入 1 到 n 的每一个数字
for (int i = 1; i <= n; i++) {
// 只有当这个数字 i 还没被用过时,才能放
if (vis[i] == false) {
// --- A. 尝试动作 ---
path[step] = i; // 把 i 放入第 step 个盒子
vis[i] = true; // 标记 i 已被占用
// --- B. 递归进入下一层 ---
dfs(step + 1); // 走到第 step+1 个盒子面前
// --- C. 回溯 (恢复现场) ---
// 递归回来后,我们要把 i 拿出来,标记为"未占用"
// 这样在下一次循环(i+1)时,或者回退到更上一层时,i 才能被再次使用
vis[i] = false;
// path[step] = 0; // 这行可写可不写,因为下次会被覆盖
}
}
}
int main() {
cin >> n;
dfs(1); // 从第 1 个盒子开始填
return 0;
}
2.网格图dfs
题目描述
输入一个 N×MN \times MN×M 的 01 矩阵,1 代表陆地,0 代表水。求岛屿数量。
解题逻辑(染色法)
- 遍历整个地图,找到一个还没被访问过的陆地格子。
- 计数+1(发现新大陆!)。
- 立即启动 DFS :从这个格子开始,像墨水滴在纸上一样,向四周扩散。
- 标记 :凡是 DFS 能摸到的陆地格子,全部标记为
vis = true(或者直接把地图改写成0,表示"已探索")。 - DFS 结束,继续遍历寻找下一个未访问的陆地。
代码模版
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int grid[MAXN][MAXN]; // 地图
bool vis[MAXN][MAXN]; // 访问标记
int n, m; // 行、列
// 方向数组:上 下 左 右
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
// DFS 函数:作用是把与 (x, y) 相连的所有陆地都标记掉
void dfs(int x, int y) {
// 【1. 标记当前点】
// 踩在这个格子上,先把旗插上,表示"我来过了"
vis[x][y] = true;
// 【2. 向四个方向扩散】
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
// 【3. 越界检查 (Guard)】
// 必须先检查是否跑出地图,否则数组越界会报错 (RE)
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
// 【4. 合法性检查】
// 如果是陆地(1) 且 还没来过(!vis),那就继续冲
if (grid[nx][ny] == 1 && !vis[nx][ny]) {
dfs(nx, ny); // 递归下去
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
// 读入地图
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> grid[i][j];
}
}
int islands = 0;
// 遍历每一个格子作为"潜在的起点"
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 只有当它是陆地,且没被之前的 DFS 覆盖过,才算一个新的岛屿
if (grid[i][j] == 1 && !vis[i][j]) {
islands++; // 发现新岛屿
dfs(i, j); // 把这个岛屿连带的所有陆地全部"抹掉"(标记为访问过)
}
}
}
cout << islands << endl;
return 0;
}
3.N皇后问题
题目描述:
在 N×NN \times NN×N 的棋盘上放 NNN 个皇后,要求它们不能互相攻击(即:不能在同一行、同一列、同一条对角线上)。求有多少种放法。
解题逻辑
剪枝思路(按行放):
- 我们一行一行地放(第 1 行放一个,第 2 行放一个...)。这样天然保证了**"不同行"**。
- 列剪枝:如果这一列已经有皇后了,跳过。
- 对角线剪枝:如果这两条对角线上已经有皇后了,跳过。
为了实现 O(1)O(1)O(1) 时间的快速剪枝,我们需要 3 个数组来记录状态:
col[x]:第 xxx 列有没有皇后。dg[x+y]:正对角线(y=x+b⇒b=y−xy=x+b \Rightarrow b=y-xy=x+b⇒b=y−x)。为防负数,通常用 y−x+Ny-x+Ny−x+N。udg[x-y]:反对角线(y=−x+b⇒b=y+xy=-x+b \Rightarrow b=y+xy=−x+b⇒b=y+x)。
代码模版
cpp
#include <iostream>
using namespace std;
const int N = 20; // 稍微开大点
int n;
char g[N][N]; // 棋盘
bool col[N], dg[N], udg[N]; // 剪枝用的标记数组
// u: 当前正在处理第 u 行
void dfs(int u) {
// 【1. 截止条件】
if (u == n) {
// 找到了一个解,输出棋盘
for (int i = 0; i < n; i++) puts(g[i]);
puts(""); // 换行
return;
}
// 【2. 遍历当前行的每一列】
for (int i = 0; i < n; i++) {
// --- ✂️ 核心剪枝逻辑 ---
// 如果 第i列没被占 AND 正对角线没被占 AND 反对角线没被占
// 注意:正对角线下标 u+i,反对角线下标 n-u+i (防止负数)
if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
// --- A. 尝试放皇后 ---
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = true; // 标记占位
// --- B. 递归下一行 ---
dfs(u + 1);
// --- C. 回溯 (恢复现场) ---
col[i] = dg[u + i] = udg[n - u + i] = false; // 取消标记
g[u][i] = '.';
}
}
}
int main() {
cin >> n;
// 初始化棋盘
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
g[i][j] = '.';
dfs(0); // 从第 0 行开始放
return 0;
}
4.树的DFS
第一部分:存树与基本遍历
1.核心工具:邻接表 (Adjacency List)
想象每个节点都有一个"通讯录"。
vector<int> adj[N]:adj[1]是一个列表,里面记着节点 1 的所有邻居{2, 5, 9}。
2.核心方法:dfs(u, fa)
在网格图里,我们用 vis 数组防止走回头路。 在树里,因为没有环(死循环),我们只需要防止"刚从爸爸那儿来,又扭头去找爸爸"。
- u:我现在在哪(当前节点)。
- fa:我是从哪来的(爸爸节点)。
- 规则 :遍历邻居时,只要
邻居 != fa,就大胆往下走。
通过题目引入 :
题目:求树的深度
目标 :输入一棵 NNN 个节点的树(默认 1 号点是根),求出每个节点距离根节点有多远(深度)。
代码模版
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 100005; // 节点最大数量
vector<int> adj[N]; // 邻接表:adj[u] 存 u 的所有邻居
int deep[N]; // deep[u] 存 u 的深度
// u: 当前节点
// fa: 父亲节点 (防止回头)
// d: 当前深度
void dfs(int u, int fa, int d) {
deep[u] = d; // 1. 记录当前节点的深度
// 2. 遍历 u 的所有邻居 v
for (int v : adj[u]) {
// 【核心判断】只要邻居不是爸爸,就是儿子,继续往下搜
if (v != fa) {
dfs(v, u, d + 1); // 这里的 u 变成了下一层的爸爸,深度 +1
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
// 读入 n-1 条边
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
// 无向边,两边都要加进对方的通讯录
adj[u].push_back(v);
adj[v].push_back(u);
}
// 从 1 号点开始搜,设 1 号点的父亲是 0 (不存在),深度是 1
dfs(1, 0, 1);
// 输出结果看看对不对
for (int i = 1; i <= n; i++) {
cout << "Node " << i << " Depth: " << deep[i] << endl;
}
return 0;
}
第二部分:带权树
核心变化:
- 存图升级 :以前
adj[u]只存邻居v,现在要存{邻居 v, 距离 w}。- 我们需要用到
pair<int, int>。 vector<pair<int, int>> adj[N];
- 我们需要用到
- DFS升级 :递归时传递的距离不再是
+1,而是+w。
经典问题 :大臣的旅费 (双DFS求直径)
题目描述:
很久以前,T王国空前繁荣。为了方便,工匠们修筑了 N−1N-1N−1 条道路将王国里 NNN 个城市连在了一起(这就构成了一棵树)。大臣要巡视,他希望走的路程越远越好(以此多报销旅费)。旅费计算公式:如果路程是 SSS,费用是 S×10+S(S+1)/2S \times 10 + S(S+1)/2S×10+S(S+1)/2。请你算出大臣能报销的最大旅费。
解题思路:
公式看着吓人,其实就是 SSS 越大,费用越高。所以题目本质就是求 树的直径(树上最远两点间的距离)。
算法步骤(两次 DFS 法):
- 任选一点(比如 1 号点)出发,DFS 找到离它最远的点,记为 PPP。
- 从 PPP 点出发,再 DFS 一次,找到离 PPP 最远的点 QQQ。
- **PPP 到 QQQ 的距离就是最大路程 SSS **。
- 套公式算出旅费。
代码模版
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
// 【变化1】存图升级:pair<邻居, 边权>
// adj[u] = {{v1, w1}, {v2, w2}...}
vector<pair<int, int>> adj[N];
int n;
int max_dist = 0; // 记录当前找到的最大距离
int farthest_node = 0; // 记录走到了哪个点(最远点)
// u: 当前点
// fa: 父亲点
// current_dist: 从起点走到现在的累计距离
void dfs(int u, int fa, int current_dist) {
// 1. 更新最大值:如果当前距离比记录的还大,更新记录
if (current_dist > max_dist) {
max_dist = current_dist;
farthest_node = u; // 记下谁是最远的
}
// 2. 遍历邻居
// auto p : adj[u] -> p 就是一个 pair<int, int>
// p.first 是邻居 v,p.second 是边权 w
for (auto p : adj[u]) {
int v = p.first;
int w = p.second;
if (v != fa) { // 只要不是父亲
// 【变化2】递归下去,距离累加 w
dfs(v, u, current_dist + w);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
// 读入 N-1 条边
for (int i = 0; i < n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
// 无向图,两边都要存
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
// --- 第一步:从 1 号点出发找最远的 P ---
max_dist = 0;
dfs(1, -1, 0); // 假设 1 号点的父亲是 -1
int P = farthest_node;
// --- 第二步:从 P 点出发找最远的 Q ---
max_dist = 0; // 记得重置!
dfs(P, -1, 0);
int S = max_dist; // 这就是直径长度
// --- 第三步:计算旅费 ---
// 公式:S*10 + S*(S+1)/2
// 注意:如果是 C++,计算结果可能很大,可以用 long long
long long ans = S * 10 + (long long)S * (S + 1) / 2;
cout << ans << endl;
return 0;
}
**第三部分:树形DP **(重点)
放在了动态规划部分。
BFS
1.网格图最短路
题目:逃离实验室
你被困在一个 N×MN \times MN×M 的实验室里。
.代表空地,可以走。#代表墙壁,不能走。S代表你的起点。E代表出口。
输入格式:
第一行 N,MN, MN,M (1≤N,M≤2001 \le N, M \le 2001≤N,M≤200)。
接下来 NNN 行,每行 MMM 个字符,代表地图。
输入样例:
cpp
5 5
S..##
.#...
...#.
.###.
...E.
(注意:这里没直接给坐标数字,你需要自己扫描地图找到 S 和 E 的坐标)
任务:求出从S到E的最短路径。
代码模版
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
int N, M;
int sx, sy, ex, ey; // 起点和终点坐标
char grid[MAXN][MAXN];
int dist[MAXN][MAXN];
// 方向数组
int dirs[4][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}};
int bfs() {
// 1. 初始化
memset(dist, -1, sizeof(dist));
queue<pair<int, int>> q; // 【建议】队列放在局部变量,自动清空
// 2. 起点入队
dist[sx][sy] = 0;
q.push({sx, sy}); // 【修正语法】
while(!q.empty()) {
auto cur = q.front();
q.pop();
int x = cur.first;
int y = cur.second;
// 到达终点
if(x == ex && y == ey) return dist[x][y];
// 遍历四个方向
for(auto dir : dirs) {
int cx = x + dir[0];
int cy = y + dir[1];
// 判断条件
// 1. 不越界
// 2. 不是墙 (注意:只要不是 '#' 就可以走,包括 'E' 和 '.')
// 3. 没走过
if(cx >= 0 && cy >= 0 && cx < N && cy < M &&
grid[cx][cy] != '#' && dist[cx][cy] == -1) {
dist[cx][cy] = dist[x][y] + 1;
q.push({cx, cy});
}
}
}
return -1; // 走不到终点
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N >> M;
for(int i = 0; i < N; ++i) {
for(int j = 0; j < M; ++j) {
cin >> grid[i][j];
if(grid[i][j] == 'S') {
sx = i; sy = j;
}
else if(grid[i][j] == 'E') {
ex = i; ey = j;
}
}
}
// 调用 bfs
cout << bfs() << endl;
return 0;
}
2.连通块问题
通俗理解
想象一张画纸上有很多独立的墨迹。
- 最短路模型:问你墨迹 A 到墨迹 B 要走多远。
- 连通块模型 :问你纸上一共有几坨 墨迹?或者最大的那坨墨迹面积是多少?
核心逻辑:双重循环 + BFS
这道题和迷宫最短路唯一的区别在于:起点不止一个,而且我们不知道起点在哪。
算法流程
- 扫描全图 :用双重循环 (
i从 0 到 N,j从 0 到 M) 遍历每一个格子。 - 发现新大陆 :如果遇到一个格子是 "陆地" 且 "没被访问过" :
- 岛屿数量 + 1。
- 立即触发 BFS:把这个格子扔进队列。
- 染色 (BFS 过程) :
- 只要队列不空,就取出队头,把它的上下左右邻居(如果是陆地且没访问过)统统标记为"已访问",并扔进队列。
- 目的:把这整块相连的陆地"抹平",防止下次循环时重复计算。
题目:最大的岛屿
给你一个 N×MN \times MN×M 的矩阵(0水,1陆地)。 请你计算出面积最大 的那座岛屿的面积是多少?(面积 = 包含的 1 的个数)。
代码模版
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int n, m;
int grid[MAXN][MAXN];
int vis[MAXN][MAXN];
int dirs[4][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}};
// 让 BFS 有返回值,返回当前找到的岛屿面积
int bfs(int x, int y) {
int current_area = 1; // 局部计数器
vis[x][y] = 1;
// 【修正1】补全尖括号
queue<pair<int, int>> q;
q.push({x, y});
while(!q.empty()) {
auto cur = q.front();
q.pop();
// 必须加引用 &,否则数组无法复制
for(auto &dir : dirs) {
int cx = cur.first + dir[0];
int cy = cur.second + dir[1];
// 越界检查 + 没访问过 + 是陆地
if(cx >= 0 && cy >= 0 && cx < n && cy < m &&
vis[cx][cy] == 0 && grid[cx][cy] == 1) {
vis[cx][cy] = 1;
current_area++; // 面积 +1
q.push({cx, cy});
}
}
}
return current_area; // 返回这块岛屿的总面积
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for(int i = 0; i < n; ++i) {
for(int j = 0; j < m; ++j) {
cin >> grid[i][j];
}
}
// 初始化 vis 数组(其实全局变量默认是0,这句不写也行,但写了更保险)
memset(vis, 0, sizeof(vis));
// 初始化为 0,防止全水地图输出 -1
int max_area = 0;
for(int i = 0; i < n; ++i) {
for(int j = 0; j < m; ++j) {
// 只有遇到没访问过的陆地,才启动 BFS
if(grid[i][j] == 1 && vis[i][j] == 0) {
// BFS 返回当前岛屿面积,尝试更新最大值
int area = bfs(i, j);
max_area = max(max_area, area);
}
}
}
cout << max_area;
return 0;
}
3.多源 BFS
1. 场景想象:僵尸爆发
- 单源 BFS :地图上只有 1 个 僵尸(起点),问你多久能感染全城?
- 这就好比往水里扔 1 颗 石子,波纹一圈圈扩散。
- 多源 BFS :地图上有 5 个 僵尸(多个起点),它们 同时 开始咬人。问你全城多久沦陷?
- 这就好比往水里 同时扔一把 石子,多个波纹同时扩散,还会互相融合。
2. 核心逻辑:初始化的艺术
很多同学遇到"多起点"时,第一反应是:
- "能不能对每个起点分别跑一次 BFS,然后取最小值?"
- 千万别! 如果有 10510^5105 个起点,跑 10510^5105 次 BFS 直接超时(Time Limit Exceeded)。
正确做法:
在 BFS 开始前,把所有"起点"统统塞进队列!
并且把它们的距离 dist 都设为 0,其他点设为 -1。
这样,队列里一开始就有了一堆"0层节点"。
-
第一轮循环,它们会集体向外扩展出"1层节点"。
-
第二轮循环,这些"1层节点"再集体扩展出"2层节点"。
-
...
BFS 会自动模拟出"多点并发扩散"的效果,就像它们是一个超级起点的分身一样。
3.题目
给你一个 N×MN \times MN×M 的地图,1 代表传染源,0 代表健康人。传染源每秒钟向上下左右扩散一格。求:地图上每个健康人被感染的最早时间?
4.代码模版
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n, m;
int grid[MAXN][MAXN]; // 0:健康, 1:源头
int dist[MAXN][MAXN]; // 记录感染时间,同时充当 visited
int dirs[4][2] = {{0,1}, {0,-1}, {1,0}, {-1,0}};
void bfs() {
queue<pair<int, int>> q;
// --- 【关键点】初始化 ---
// 1. 先把所有距离初始化为 -1
memset(dist, -1, sizeof(dist));
// 2. 遍历全图,找到所有源头,统统入队!
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(grid[i][j] == 1) {
q.push({i, j}); // 所有源头同时入队
dist[i][j] = 0; // 源头的时间是 0
}
}
}
// --- 开始 BFS (和普通 BFS 一模一样) ---
while(!q.empty()) {
auto t = q.front();
q.pop();
int x = t.first;
int y = t.second;
for(auto dir : dirs) {
int nx = x + dir[0];
int ny = y + dir[1];
// 越界检查 + 没被感染过(-1)
// 注意:因为是求最早感染时间,所以只要被访问过一次,那次肯定是最短的
if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1; // 时间 + 1
q.push({nx, ny});
}
}
}
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cin >>grid[i][j];
}
}
bfs();
// 输出每个点被感染的时间
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cout <<dist[i][j];
}
cout <<'\n';
}
return 0;
}
4.隐式图BFS
1. 什么是"隐式图"?
在之前的题目里,地图是画出来 的(N×MN \times MN×M 的矩阵),你看得见、摸得着。
但在隐式图中,图是看不见的。
- 节点 (Node) :不再是坐标
(x, y),而是一个 "状态" 。- 比如:一个字符串
"12345",或者一个结构体,或者魔方的一个面。
- 比如:一个字符串
- 边 (Edge) :不再是"上下左右走一格",而是 "一次操作" 。
- 比如:交换两个字符、数字加减、马走日等。
经典案例:
- 字串变换 :把
"abc"变成"def",每次只能变一个字母,最少几步? - 华容道/八数码:移动空格,把乱序数字还原,最少几步?
2. 核心工具升级
既然没有 x,yx, yx,y 坐标了,我们的工具箱必须升级:
- 队列变了 :
- 旧:
queue<pair<int, int>> - 新:
queue<string>(或者queue<int>,queue<struct>)
- 旧:
- 判重数组变了 :
- 旧:
dist[N][N](用坐标查步数) - 新:
unordered_map<string, int> dist(用"样子"查步数) - 解释 :
dist["abc"] = 5表示变成 "abc" 这个样子最少需要 5 步。
- 旧:
3.字符串变换
题目 :给定起点字符串 start 和终点字符串 end。每次操作可以交换相邻的两个字符。问最少交换几次能把 start 变成 end?
cpp
#include <bits/stdc++.h>
using namespace std;
string start_s, end_s;
int bfs() {
// 1. 定义队列和距离表
queue<string> q;
unordered_map<string, int> dist; // 记录 string -> step
// 2. 起点入队
q.push(start_s);
dist[start_s] = 0; // 起点步数为 0
// 3. 开始 BFS
while (!q.empty()) {
string cur = q.front();
q.pop();
// 记录当前步数,方便后面计算
int d = dist[cur];
// (1) 终点检查
if (cur == end_s) return d;
// (2) 尝试所有可能的"操作" (扩展节点)
// 规则:交换相邻两个字符
// 字符串长度为 N,有 N-1 种交换位置
for (int i = 0; i < cur.size() - 1; i++) {
string next_s = cur;
swap(next_s[i], next_s[i+1]); // 模拟操作
// (3) 判重:如果这个样子没见过
if (dist.find(next_s) == dist.end()) {
dist[next_s] = d + 1; // 步数 +1
q.push(next_s);
}
}
}
return -1; // 变不出来
}
int main() {
cin >> start_s >> end_s;
cout << bfs() << endl;
return 0;
}
4.八数码问题
题目 :输入一个初始状态(如 123405678),求变到目标状态 123456780 最少几步? (3列3行,九宫格,0代表空格)
cpp
#include <bits/stdc++.h>
using namespace std;
// 目标状态
const string TARGET = "123456780";
// 方向数组:上、下、左、右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int bfs(string start) {
// 1. 定义队列和距离表
queue<string> q;
unordered_map<string, int> dist;
// 2. 起点入队
q.push(start);
dist[start] = 0;
// 3. 开始 BFS
while (!q.empty()) {
string cur = q.front();
q.pop();
int d = dist[cur];
// (1) 到达目标了吗?
if (cur == TARGET) return d;
// (2) 寻找 '0' 的位置 k
int k = cur.find('0');
// (3) 将一维下标 k 还原为二维坐标 (x, y)
int x = k / 3;
int y = k % 3;
// (4) 尝试往 4 个方向移动 '0'
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
// 边界检查
if (nx >= 0 && nx < 3 && ny >= 0 && ny < 3) {
// 计算移动后 '0' 的新一维下标 nk
int nk = nx * 3 + ny;
// --- 状态转移 ---
string next_s = cur;
swap(next_s[k], next_s[nk]); // 交换 0 和邻居
// --- 判重 ---
if (dist.find(next_s) == dist.end()) {
dist[next_s] = d + 1;
q.push(next_s);
}
}
}
}
return -1; // 无法到达
}
int main() {
string start_s;
cin >> start_s; // 输入例如:283104765
cout << bfs(start_s) << endl;
return 0;
}