【洛谷】P7771 【模板】欧拉路径

题目地址:

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 uv的有向边。

输出格式:
如果不存在欧拉路径,输出一行No。否则输出一行 m + 1 m+1 m+1个数字,表示字典序最小的欧拉路径。

数据范围:
对于 50 % 50\% 50%的数据, n , m ≤ 1 0 3 n,m≤10^3 n,m103
对于 100 % 100\% 100%的数据, 1 ≤ u , v ≤ n ≤ 1 0 5 1≤u,v≤n≤10^5 1u,vn105 m ≤ 2 × 1 0 5 m≤2×10^5 m2×105。保证将有向边视为无向边后图连通。

题目已经保证图视为无向图时连通。首先要判断欧拉路径是否存在。如果存在欧拉回路,则每个点入度等于出度,反之也成立;如果不存在欧拉回路但存在欧拉通路,则其中 n − 2 n-2 n2个点入度等于出度,另外两个点,一个出度比入度多 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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值