题解:洛谷 P6066 Watchcow
【题目来源】
洛谷:P6066 [USACO05JAN] Watchcow S - 洛谷
【题目描述】
Farmer John 有 \(N\) 个农场(\(2 \leq N \leq 10^4\)),这些农场由 \(M\) 条道路连接(\(1 \leq M \leq 5 \times 10^4\))。不保证没有重边。
Bassie 从 \(1\) 号农场开始巡逻,每条路必须从两个方向各走恰好一遍,最后回到 \(1\) 号农场。
请输出一条满足上述要求的路径。
保证这样的路径存在。如果有多条路径,任意输出一条即可。
【输入】
第一行两个整数 \(N,M\)。
接下来 \(M\) 行,每行两个整数 \(u,v\),描述一条 \(u\) 到 \(v\) 的道路。
【输出】
输出经过的农场,一行一个。
【输入样例】
4 5
1 2
1 4
2 3
2 4
3 4
【输出样例】
1
2
3
4
2
1
4
3
2
4
1
【核心思想】
-
问题分析:给定 \(N\) 个农场、\(M\) 条无向边(可能有重边),要求从农场 \(1\) 出发,每条边从两个方向各走恰好一遍,最后回到 \(1\)。输出一条满足条件的路径。这是一个欧拉回路(双向遍历版本)问题,关键在于将无向边的每条边视为两条有向边,求每条有向边恰好经过一次的欧拉回路。
-
算法选择:
- DFS 找欧拉回路(Hierholzer 算法):通过深度优先搜索,在回溯时将节点加入路径,得到欧拉回路
- 边删除标记:访问一条边后将其终点标记为 \(0\)(相当于删除该有向边),避免重复访问
- 后序输出:欧拉回路的路径在 DFS 回溯时记录,需逆序(或后序)输出
-
关键步骤:
- 建图:读入 \(N, M\) 和 \(M\) 条边。对每条无向边 \((u, v)\),添加两条有向边 \(u \to v\) 和 \(v \to u\)
- DFS 遍历(从节点 \(1\) 开始):
- 遍历当前节点 \(u\) 的所有邻接边
- 对每条未访问的边(终点不为 \(0\)):
- 标记该边为已访问(
e[i] = 0) - 递归访问邻接节点
dfs(j)
- 标记该边为已访问(
- 回溯时将 \(u\) 加入队列
q.push(u)
- 输出路径:依次输出队列中的节点
-
时间/空间复杂度:
- 时间复杂度:\(O(N + M)\),每条边被访问恰好一次
- 空间复杂度:\(O(N + M)\),邻接表存储和队列
-
DFS 欧拉回路的核心思想:
- 有向边建模:将无向边的"双向各走一遍"转化为两条独立的有向边,问题变为求每条有向边恰好经过一次的欧拉回路
- 边删除保证唯一性:访问后将边标记为 \(0\),确保每条有向边只被遍历一次
- 后序记录路径:DFS 回溯时才将节点加入路径,这样得到的是从叶子回到根的顺序,恰好是欧拉回路的正确顺序
- Hierholzer 算法的递归实现:该算法核心思想是"尽可能走,走不通时回溯并记录",保证每条边都被覆盖且路径连续
- 存在性保证:题目保证路径存在,即图是欧拉图(所有节点度数为偶数,且连通)
- 适用于"每条边双向遍历一次的欧拉回路"问题,核心在于有向边建模和 DFS 边删除策略
【解题思路】

【算法标签】
普及+ #DFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义常量
const int N = 10005; // 最大顶点数
const int M = 50005 * 2; // 最大边数,无向图需要乘以2
int n, m; // n: 顶点数, m: 边数
int h[N]; // 邻接表头数组,h[u]表示顶点u的第一条边的索引
int e[M]; // 边数组,e[i]表示第i条边的终点顶点
int ne[M]; // 邻接表next数组,ne[i]表示下一条边的索引
int idx; // 边的索引计数器
queue<int> q; // 队列,用于存储DFS后序遍历的顶点
// 添加边的函数
void add(int a, int b)
{
e[idx] = b; // 记录边的终点
ne[idx] = h[a]; // 新边指向原来链表的头
h[a] = idx++; // 更新链表头,并递增索引
}
// 深度优先搜索函数
void dfs(int u)
{
// 遍历顶点u的所有邻接边
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i]; // 获取邻接顶点
if (j) // 如果邻接顶点存在
{
e[i] = 0; // 标记当前边为已访问
dfs(j); // 递归访问邻接顶点
}
}
q.push(u); // 后序遍历:在访问完所有邻接顶点后,将当前顶点加入队列
}
int main()
{
// 初始化邻接表头数组为-1
memset(h, -1, sizeof(h));
// 读入顶点数和边数
cin >> n >> m;
// 读入每条边
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
add(u, v); // 添加u到v的边
add(v, u); // 添加v到u的边(无向图需要双向添加)
}
// 从顶点1开始深度优先搜索
dfs(1);
// 输出DFS后序遍历的结果
while (!q.empty())
{
cout << q.front() << endl;
q.pop();
}
return 0;
}
【运行结果】
4 5
1 2
1 4
2 3
2 4
3 4
1
2
3
2
1
4
2
4
3
4
1
浙公网安备 33010602011771号