题目地址:
https://www.luogu.com.cn/problem/P7771
题目描述:
求有向图字典序最小的欧拉路径。
输入格式:
第一行两个整数
n
,
m
n,m
n,m表示有向图的点数和边数。接下来
m
m
m行每行两个整数
u
,
v
u,v
u,v表示存在一条
u
→
v
u→v
u→v的有向边。
输出格式:
如果不存在欧拉路径,输出一行No
。否则输出一行
m
+
1
m+1
m+1个数字,表示字典序最小的欧拉路径。
数据范围:
对于
50
%
50\%
50%的数据,
n
,
m
≤
1
0
3
n,m≤10^3
n,m≤103。
对于
100
%
100\%
100%的数据,
1
≤
u
,
v
≤
n
≤
1
0
5
1≤u,v≤n≤10^5
1≤u,v≤n≤105,
m
≤
2
×
1
0
5
m≤2×10^5
m≤2×105。保证将有向边视为无向边后图连通。
题目已经保证图视为无向图时连通。首先要判断欧拉路径是否存在。如果存在欧拉回路,则每个点入度等于出度,反之也成立;如果不存在欧拉回路但存在欧拉通路,则其中 n − 2 n-2 n−2个点入度等于出度,另外两个点,一个出度比入度多 1 1 1(该点为起点),另一个入度比出度多 1 1 1(该点为终点),这也是充分必要条件。如果存在欧拉回路,并且不考虑字典序限制,则起点可以任意取。所以这道题必须先判断是否存在欧拉路径。如果存在,那么可以采用相关算法。
如果没有字典序最小这个条件,那么从起点开始,直接一遍DFS,保证每条边只走一次,并且在回溯的时候将当前点加入一个列表,则列表逆序即为一个从起点出发的欧拉路径。如果加上字典序限制的话,每次选出边的时候,必须选字典序最小的那个点对应的边(这一点可以这样理解,DFS回溯加入顶点已经保证能走出欧拉路径了,那么每次优先挑没走过的字典序最小的点的路径,那么回溯的时候该点就会靠后加入答案,逆序之后该点就会靠前,从而使得整体字典序最小)。由于要将边按照其指向的点的字典序排序,我们用邻接表vector<int> G[]
来存图,这样方便排序。同时,求欧拉路径有删边的操作,我们可以不真的从vector
里删掉,而是记录一下每个点的出边已经删了多少条,这样下次遍历的时候从下一条边开始遍历即可。代码如下:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
int n, m;
int deg[N][2];
vector<int> G[N];
// del[x]是x的出边已经删了多少条,即走过多少条
int res[M], idx, del[N], cnt[3];
void dfs(int u) {
// 按指向点的字典序遍历出边,略过已经删掉的边
for (int i = del[u]; i < G[u].size(); i = del[u]) {
del[u]++;
dfs(G[u][i]);
}
res[idx++] = u;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
G[a].push_back(b);
deg[a][1]++, deg[b][0]++;
}
int S = 1;
for (int i = 1; i <= n; i++) {
if (deg[i][0] != deg[i][1]) cnt[0]++;
// 如果存在出度比入度多1的点,那么如果存在欧拉通路,则起点为这个点
if (deg[i][1] - deg[i][0] == 1) cnt[1]++, S = i;
if (deg[i][0] - deg[i][1] == 1) cnt[2]++;
}
// 如果cnt[0]不为0则不存在欧拉回路;如果入度比出度多1的点
// 不为1或者出度比入度多1的点不为1,则不存在欧拉通路
if (cnt[0] && (cnt[1] != 1 || cnt[2] != 1)) return !puts("No");
for (int i = 1; i <= n; i++) sort(G[i].begin(), G[i].end());
dfs(S);
for (int i = idx - 1; i >= 0; i--) printf("%d ", res[i]);
}
时间复杂度 O ( m ) O(m) O(m),空间 O ( n ) O(n) O(n)。