JOJO教你欧拉图

欧拉图的定义

欧拉回路:在一个图中,从某个顶点出发,经过每条边恰好一次,最终又回到起始顶点的闭合路径。
(人话:走一圈,不走重复的路,把所有的路走完,回到原点
欧拉通路:一个图中,从某个顶点出发,经过每条边恰好一次,但不要求回到起始顶点的路径。欧拉通路也可以被称为欧拉路径
(人话:走一圈,不走重复的路,把所有的路走完,在任意地方停止
欧拉图:具有欧拉回路的图。
半欧拉图:具有欧拉通路但不具有欧拉回路的图。

无向图的欧拉路径

无向欧拉图判别法

1. 无向图是连通图
2. 顶点的度数都是偶数

解析:(长篇大论,可看可不看)

  1. 进入和离开:在一条路径中,当你到达一个顶点时,你是通过一条边进入这个顶点的,而离开这个顶点时又需要通过另一条边。因此,每次到达一个顶点,都会消耗一条边。
  2. 边的使用:如果一个顶点的度数是偶数,意味着有偶数条边连接到这个顶点。这样,在遍历过程中,你可以成对地进入和离开这个顶点,确保每条边都被使用一次并且能够最终回到起始点。
  3. 奇数度数的顶点:如果一个顶点的度数是奇数,那么在遍历结束时,你会“多出”一条边,无法成对地离开这个顶点,从而无法形成一个闭合的欧拉回路。

无向半欧拉图判别法

1. 无向图是连通图
2. 恰有 2 个奇度顶点

解析:(长篇大论,可看可不看)

  1. 进入和离开的边:在图中,每次进入一个顶点时,都会使用一条边,而每次离开一个顶点时也会使用一条边。若一个顶点的度数是偶数,意味着进入和离开的边数是相等的,能够成对使用。

  2. 奇度顶点的特性:如果一个顶点的度数是奇数,表示有奇数条边连接到这个顶点。在遍历过程中,进入这个顶点的边数和离开的边数不能完全配对,因此会有一条边未被配对。

  3. 路径的起点和终点:在一条欧拉路径中,必须有一个起点和一个终点。为了使得路径能够开始和结束,起点的度数必须是奇数(因为你需要一条边进入,而没有边离开),终点的度数也必须是奇数(因为你需要一条边离开,而没有边进入)。

例题: [USACO3.3] 骑马修栅栏 Riding the Fences

无向图的欧拉路径模板

题目概括:(不想看题,看这里)

无向图,给定一个整数 m ,接下来 m 行,每行 两个整数u v,表示 u,v之间连通。
求字典序最小(起始点编号最小)的 欧拉路径。保证答案有解

题目背景

Farmer John 每年有很多栅栏要修理。他总是骑着马穿过每一个栅栏并修复它破损的地方。

原题描述

John 是一个与其他农民一样懒的人。他讨厌骑马,因此从来不两次经过一个栅栏。

John 的农场上一共有 m m m 个栅栏,每一个栅栏连接两个顶点,顶点用 1 1 1 500 500 500 标号(虽然有的农场并没有那么多个顶点)。一个顶点上至少连接 1 1 1 个栅栏,没有上限。两顶点间可能有多个栅栏。所有栅栏都是连通的(也就是你可以从任意一个栅栏到达另外的所有栅栏)。John 能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。

你需要求出输出骑马的路径(用路上依次经过的顶点号码表示),使每个栅栏都恰好被经过一次。如果存在多组可行的解,按照如下方式进行输出:如果把输出的路径看成是一个 500 500 500 进制的数,那么当存在多组解的情况下,输出 500 500 500 进制表示法中最小的一个 (也就是输出第一位较小的,如果还有多组解,输出第二位较小的,以此类推)。

输入数据保证至少有一个解。

输入格式

第一行一个整数 m m m,表示栅栏的数目。

从第二行到第 ( m + 1 ) (m+1) (m+1) 行,每行两个整数 u , v u,v u,v,表示有一条栅栏连接 u , v u,v u,v 两个点。

输出格式

( m + 1 ) (m+1) (m+1) 行,每行一个整数,依次表示路径经过的顶点号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。

数据保证至少有一组可行解。

样例 #1

样例输入 #1

9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6

样例输出 #1

1
2
3
4
2
5
4
6
5
7

提示

对于 100 % 100\% 100% 的数据, 1 ≤ m ≤ 1024 , 1 ≤ u , v ≤ 500 1 \leq m \leq 1024,1 \leq u,v \leq 500 1m1024,1u,v500

题目翻译来自NOCOW。

USACO Training Section 3.3

问题解决

题目说:

John 能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。他讨厌骑马,因此从来不两次经过一个栅栏。

所以可以推断出,这是考察无向半欧拉图的题目,可以记录每个点的度,如果这个点的度为奇数,那么就从这个点开始。因为

2. 恰有 2 个奇度顶点

使用DFS进行遍历,先处理子问题,在将当前节点记录
注:不能先记录当前节点
解析:(可看可不看)(记住坑点就行)

  1. 路径构建:在寻找欧拉路径时,我们需要按照边的顺序来构建路径。DFS会尽可能深入地探索每一个分支,直到无法继续为止。当我们回溯到某个节点时,意味着我们已经完成了从这个节点出发的所有可能路径。

  2. 记录当前节点:在DFS的过程中,当我们访问一个节点并沿着一条边前进时,我们会将这条边标记为已访问(通常是通过删除或修改边的状态),然后继续深入探索。如果在某个节点的所有边都已被访问完,我们就需要将当前节点记录下来,以便在回溯时能够正确地构建出路径。

  3. 路径的顺序:在DFS完成后,我们通常会得到一个从起点到终点的路径,但实际的欧拉路径是从起点出发,经过图中每条边恰好一次后到达终点。因此,记录当前节点是为了确保在回溯时能够正确地反向构建出完整的路径。

  4. 栈的使用:DFS通常使用栈来管理节点。在回溯时,栈的特性(后进先出)会帮助我们按照正确的顺序来构建路径。记录当前节点的过程实际上是将节点推入栈中,以便在完成DFS后能够按顺序输出路径。

例如:


如果我们先记录1,然后访问2,就无法访问3,所以应该将1号节点放置最后。

代码

#include <bits/stdc++.h>

#define endl '\n'

using namespace std;

int mp[505][505]; // 因为编号不大 且 需要删除边,所以采用 邻接矩阵 存图
int du[505]; // 记录 度
int n,st = INT_MAX; // 因为节点编号不连续,所以要找到最小的节点编号,以保证字典序最小
vector<int> ans; // 记录答案
void dfs(int now)
{
    for(int i=1;i<=500;i++){
        if(mp[now][i]>0){
            mp[now][i]--,mp[i][now]--; 
            du[i]--,du[now]--;
            dfs(i);
        }
    }
    ans.push_back(now); // 最后记录答案
}
int main()
{
    cin >> n;
    for(int i=1;i<=n;i++){
        int u,v; cin >> u >> v;
        mp[u][v]++,mp[v][u]++;
        du[u]++,du[v]++;
        st = min({st,u,v}); // 记录最小节点
    }
    for(int i=1;i<=500;i++) if(du[i] & 1) {st = i;break;} // 如果是开始节点就记录节点 i
    dfs(st);
    reverse(ans.begin(),ans.end()); // 因为 是反向记录的节点,所以反向输出
    for(auto x:ans){
        cout << x << endl;
    }
    return 0;
}

有向图的欧拉路径

有向欧拉图判别法

1. 有向图是连通图
2. 每个顶点的入度和出度相等

解析:
3. 路径的性质:在一个有向图中,路径是由边连接的。每当你从一个顶点出发沿着一条边走到另一个顶点时,你会消耗掉一条出边,同时这个目标顶点的入度会增加。因此,如果一个顶点的出度大于入度,就意味着你可以从这个顶点出发,但在某个时刻无法返回到这个顶点。

  1. 起点和终点:如果一个有向图存在欧拉回路,那么每个顶点的入度和出度必须相等,因为你必须能够从每个顶点出发并最终回到这个顶点。而如果存在欧拉路径(不要求回到起点),则只有两个顶点可以有不同的入度和出度:一个顶点可以有出度比入度多1(作为路径的起点),另一个顶点可以有入度比出度多1(作为路径的终点)。其他所有顶点的入度和出度必须相等。

  2. 流动平衡:可以将每个顶点视为一个流动的节点。为了确保流动的平衡,进入顶点的流量(入度)必须等于离开顶点的流量(出度),这样才能保证在遍历时不会出现“流入”或“流出”的不平衡情况。

有向半欧拉图判别法

1. 有向图是连通图
2. 至多一个顶点的出度与入度之差为 1
3. 至多一个顶点的入度与出度之差为 1
4. 其他顶点的入度和出度相等

解析:

  1. 路径的起点和终点

    • 如果存在一条欧拉路径,那么这条路径必须有一个起点和一个终点。起点是你开始的地方,终点是你结束的地方。
    • 在起点,出度比入度多1(意味着从这个点出发时多了一条出边),而在终点,入度比出度多1(意味着在这个点结束时多了一条入边)。
  2. 其他顶点的平衡

    • 除了起点和终点外,其他所有顶点的入度和出度必须相等。这是因为在经过这些顶点时,每次进入一个顶点时,必然也会离开这个顶点。换句话说,进入和离开的边数是相等的。
  3. 总结

    • 因此,在有向半欧拉图中,最多只有一个顶点的出度与入度之差为1(起点),最多只有一个顶点的入度与出度之差为1(终点),而其他顶点的入度和出度是相等的。

例题:【模板】欧拉路径

有向图的欧拉路径模板

题目描述

求有向图字典序最小的欧拉路径。

输入格式

第一行两个整数 n , m n,m n,m 表示有向图的点数和边数。

接下来 m m m 行每行两个整数 u , v u,v u,v 表示存在一条 u → v u\to v uv 的有向边。

输出格式

如果不存在欧拉路径,输出一行 No

否则输出一行 m + 1 m+1 m+1 个数字,表示字典序最小的欧拉路径。

样例 #1

样例输入 #1

4 6
1 3
2 1
4 2
3 3
1 2
3 4

样例输出 #1

1 2 1 3 3 4 2

样例 #2

样例输入 #2

5 5
1 2
3 5
4 3
3 4
2 3

样例输出 #2

1 2 3 4 3 5

样例 #3

样例输入 #3

4 3
1 2
1 3
1 4

样例输出 #3

No

提示

对于 50 % 50\% 50% 的数据, n , m ≤ 1 0 3 n,m\leq 10^3 n,m103

对于 100 % 100\% 100% 的数据, 1 ≤ u , v ≤ n ≤ 1 0 5 1\leq u,v\leq n\leq 10^5 1u,vn105 m ≤ 2 × 1 0 5 m\leq 2\times 10^5 m2×105

保证将有向边视为无向边后图连通。

问题解决

首先根据 有向半欧拉图判别法

至多一个顶点的入度与出度之差为 1
至多一个顶点的出度与入度之差为 1
其他顶点的入度和出度相等

所以可以对图进行判断

  • 如果某个点的 入度与出度差的绝对值 大于1 那么则无法构成欧拉路径
  • 如果 入度与出度之差为1 的点的个数大于1 那么则无法构成欧拉路径
  • 如果 出度与入度之差为1 的点的个数大于1 那么则无法构成欧拉路径

因为本题的数据较大,所以使用 邻接表 存图。那么如何删除边是另一个问题。
可以对每一个点所连的另一个点进行排序,从大到小,这样每次,取出队尾的元素,弹出队尾元素,就达到了 从小到大遍历删边 的操作。
一样现需要倒叙存入。

代码

#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+5;
const int NN = 1e5+5;
vector<int> g[N];
vector<int> ans;
int n,m;
int in[N],out[N];
void dfs(int now)
{
    while(!g[now].empty()){
        int v = g[now].back(); // 取出队尾元素
        g[now].pop_back(); // 弹出队尾元素
        dfs(v);
    }
    ans.push_back(now); // 倒叙存入
}
int main()
{
    cin >> n >> m;
    for(int i=1;i<=m;i++){
        int u,v; cin >> u >> v;
        g[u].push_back(v);
        out[u]++, in[v]++; // 记录入度与出度
    }
    for(int i=1;i<=n;i++){
        sort(g[i].begin(),g[i].end()); // 先从小到大排序
        reverse(g[i].begin(),g[i].end());// 再反转,相当于从大到小排序
    }
    int st = 1,ok = 0,inc = 0,outc = 0;
    for(int i=1;i<=n;i++){
        if(abs(in[i]-out[i])>1){
            ok = 1;
            break;
        }
        if(in[i]-out[i] == 1) outc++; // 重点个数+1
        if(out[i] - in[i] == 1) inc++,st = i; // 起点个数+1,记录起点
    }
    if(ok || outc > 1 || inc > 1){ // 如果不满足构成欧拉路径的条件
        cout << "No";
        return 0;
    }
    dfs(st);
    reverse(ans.begin(),ans.end());
    for(auto x:ans) cout << x << ' ';
    return 0;
}

总结

坑点:

  • 要倒叙存入
  • 有向、无向 欧拉图的判别 容易 错
  • 欧拉路径 就是 欧拉通路
  • 欧拉路径 包括了 欧拉回路
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值