欧拉路(有/无向图的欧拉回路)

文章介绍了欧拉图问题,包括欧拉路径和欧拉回路的概念及充要条件,并提供了C++代码实现。对于无向图和有向图的欧拉路径与回路,文章给出了判断和遍历的方法。此外,还讨论了使用并查集解决蚂蚁旅行问题,确定最少分组数量。
摘要由CSDN通过智能技术生成

引入

哥尼斯堡(Koenigsberg)七桥问题

度娘好评😍:https://baike.baidu.com/item/七桥问题/2580504?fr=ge_ala

通俗一点就是一笔画问题,而我们把这样的问题称为:欧拉图问题
【欧拉图】区分一些概念:欧拉路径、欧拉回路、欧拉图
欧拉路径:给定一个连通图,若存在一条路径,经过图中每条边一次且仅一次(一笔画)
欧拉回路:在欧拉路径的基础上,需要最终回到起点
欧拉图:具有欧拉回路的图,称为欧拉图

图如果要存在欧拉路,就必须是所有边要连通的


在无向图下:
(1)欧拉路径的充要条件:奇点为0个或者是2个
(2)欧拉回路的充要条件:奇点为0个
有向图:
(1)欧拉路径的充要条件:要么所有点的出入度都相等;要么除了起点和终点外,所有点的出入度相等,起点的出度比入度多1,终点的入度比出度多1
(2)欧拉回路的充要条件:有点的出入度都相等

上面这些条件一定要记准确!!!


欧拉路

题目描述
有一个图,图中要么有两个奇点要么0奇点,如果是欧拉回路请从第一个点为起点开始遍历,如果有两个奇点,则以字典序小的为起点开始遍历,在遍历的过程中,字典序小的先遍历

输入描述
第一行两个整数,n和e,表示有n个节点,e条边,n<50.

输出描述
只有一行,为欧拉路或欧拉回路。

样例
输入 5 5 1 2 2 3 3 4 4 5 5 1 输出
1 2 3 4 5 1

AC code~~~

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<stack>
#include<map>
#define TIE ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define N 110
#define INF 0x3f3f3f3f
using namespace std;
int a[N][N],du[N],ans[N];
int n,e,cnt,s=1;
void dfs(int x){
	for(int i=1;i<=n;++i){
		if(a[x][i]){
			a[x][i]--,a[i][x]--;//走边,所以这里直接删边 
			dfs(i);
		}
	}
	ans[++cnt]=x;
}
int main(){
	TIE;cin>>n>>e;
	for(int i=1;i<=e;++i){
		int u,v;
		cin>>u>>v;
		a[u][v]++,a[v][u]++;
		du[u]++,du[v]++;
	}
	for(int i=1;i<=n;++i){
		if(du[i]&1){
			s=i;
			break;
		}
	}
	dfs(s);
	for(int i=cnt;i>=1;i--) cout<<ans[i]<<" ";//回溯时存的,所以倒序输出 
	return 0;
}

欧拉回路

题目描述 有一天一位灵魂画师画了一张图,现在要你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次。 一共两个子任务:

  1. 这张图是无向图。
  2. 这张图是有向图。

输入描述 第一行一个整数 t,表示子任务编号t∈1,2,如果 t=1 则表示处理无向图的情况,如果t=2 则表示处理有向图的情况 第二行两个整数 n,m,表示图的结点数和边数 接下来 m 行中,第 i 行两个整数 ,vi,ui,表示第 i 条边(从 1开始编号)保证 1≤vi,ui≤n。

  1. 如果 t=1 则表示 vi 到 ui 有一条无向边。
  2. 如果 t=2 则表示 vi 到 ui 有一条有向边 图中可能有重边也可能有自环。

输出描述 如果不可以一笔画,输出一行 “NO”。

否则,输出一行 “YES”,接下来一行输出一组方案。

在这里插入图片描述

输入样例1
1
3 3
1 2
2 3
1 3
输出样例1
YES
3 -2 -1
输入样例2
2
5 6
2 3
2 5
3 4
1 2
4 2
5 1
输出样例2
YES
4 1 3 5 2 6
数据范围 全部数据: 1≤N≤105 ​​ 0≤m≤2×105

​​
AC code ~~~

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<stack>
#include<map>
#define TIE ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define N 100100
#define M 200100
#define INF 0x3f3f3f3f
using namespace std;
int t, n, m, cnt, k;
int head[N], to[M * 2], nxt[M * 2], idx = -1, ru[N], chu[N], ans[M * 2], vis[M * 2];
void add(int u, int v) {
	to[++idx] = v, nxt[idx] = head[u], head[u] = idx;
}
void dfs(int x) { //因为链式前向星是从0下标开始记录边的,所以输入边的顺序=下标+1
	for (int i = head[x]; ~i; i = nxt[i]) {
		if (vis[i]) continue;
		vis[i] = 1;
		if (t == 1) vis[i ^ 1] = 1; //无向图奇数都是反向边(仔细思考为什么),i为偶数:i^1相当于变为+1的奇数;i为奇数变为-1的偶数 
		int tmp;
		if (t == 1) {
			tmp = (i >> 1) +1; //求这是输入的第几条边(为输出做准备)
			if (i & 1) tmp = -tmp;//如果是第奇数次输入的,肯定是反向边 
		} else {
			tmp = i + 1; //有向图
		}
		dfs(to[i]);
		ans[++k] = tmp;
	}
}
int main() {
	TIE;
	memset(head, -1, sizeof head);
	cin >> t;
	cin >> n >> m;
	for (int i = 1; i <= m; ++i) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		if (t == 1) add(v, u);
		chu[u]++, ru[v]++;
	}
	if (t == 1) {
		for (int i = 1; i <= n; ++i) {
			if ((ru[i] + chu[i]) & 1) {
				cout<<"NO"<<"\n";
				return 0;
			}
		}
	} else {
		for (int i = 1; i <= n; ++i) {
			if (ru[i] != chu[i]) {
				cout<<"NO"<<"\n"; 
				return 0;
			}
		}
	}
	for (int i = 1; i <= n; ++i) {
		if (head[i] != -1) {
			dfs(i);
			break;
		}
	}
	if (k < m) {
		cout<<"NO"<<"\n";
		return 0;
	}
	cout<<"YES"<<"\n";
	for (int i = k; i >= 1; --i) cout << ans[i] << " ";
	return 0;
}


简单并查集+欧拉路

蚂蚁之旅
蚂蚁国由N个城镇组成,有M条道路连接。
托尼和他的朋友们打算走遍每一条道路,每条路都必须要走一次。然后,如果一群人只在一起是可能不能完成的,他们试图把人分成几组,每组都可以从不同的城镇开始,现在托尼想知道为了实现目标需要最少分出多少组?
输入描述
多组输入,每组之间以换行分割
对于每组数据:
第一行输入两个整数N,M,含义如题所示
然后输入M行,每行包含两个整数a,b,表示有一条道路连接城镇a和城镇b
蚂蚁国崇尚节俭,两个城市之间最多只有一条直接道路,也不会有道路连接是同一个城镇
输出描述
对于每个数据,输出答案,答案占一行。
输入样例
3 3
1 2
2 3
1 3
4 2
1 2
3 4
输出样例
1
2
提示
第一组数据图形如下所示:
在这里插入图片描述

因此只需要一组即可

第二组数据图形如下所示:
在这里插入图片描述

这个图中包含两个连通的区域,因此需要两组。
数据范围
全部数据:
1≤N≤105,0≤M≤2×105

​​冷静分析:

这个题就是让我们求有多少个连通块,并查集再好不过了。我们在输入的时候把每个通过边连接的点都并到一块,选出代表元;然后通过记录奇点来判断有多少个欧拉路径,欧拉回路需要单独判断(通过题意可知,如果是孤立点的话就不用管它,因为它没有边呀)

AC code~~~

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<stack>
#include<map>
#define TIE ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define N 200010
#define INF 0x3f3f3f3f
using namespace std;
int n,m,a,b,du[N],vis[N],father[N];
int Find(int x){
	if(x==father[x]) return x;
	return father[x]=Find(father[x]);
}
void combine(int x,int y){
	x=Find(x);
	y=Find(y);
	father[x]=y;
}
int main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		memset(du,0,sizeof du);
		memset(vis,0,sizeof vis);
		for(int i=1;i<=n;++i) father[i]=i;
		for(int i=1;i<=m;++i){
			cin>>a>>b;
			du[a]++,du[b]++;
			combine(a,b);
		}
		int num=0;
		for(int i=1;i<=n;++i){
			if(du[i]&1){
				num++;
				vis[Find(i)]=1;
			}
		}
		num/=2;
		for(int i=1;i<=n;++i){
			if(du[i]){
				if(!vis[Find(i)]&&Find(i)==i) num++;
			}
		}	
		printf("%d\n",num);
	} 
	return 0;
}


完结撒花~~~(这个颜色是真的好看)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值