欧拉通路/回路

欧拉通路是一个在小学就了解的东西,也就是一笔画。要求是构造一种方案使得在图中的每一条边都被经过恰好一次,其中点不做要求。
而欧拉回路就是要求构造出来的欧拉回路的起点和终点相同。

欧拉通路的判定是所有点的度数都为偶数或者恰好有两个是奇数。
而回路的判定则是要求所有点的度数都为偶数。
需要注意的是一般应用中我们都要求图连通

在信息学竞赛中我们一般使用的都是欧拉回路,因为回路一般可以通过添加限制来固定起点,这样可以使得不用考虑不同的起点终点同时方便判定。
我们通过欧拉回路来做一些路径必达性问题,也就是有一些边必须要经过。在大部分时候我们都不需要构造出具体的方案而是输出构造出的图的信息,比如边权和一类的。因此我们可以通过判定方便而简洁的写出代码。

P6628 [省选联考 2020 B 卷] 丁香之路

我们通过经典例题来讨论欧拉回路如何使用。

发现题目实际上等价于求定起点的欧拉路径。我们可以通过某种方式将图补全使得其有一条欧拉路径,我们要求的实际上就是这个图的边权值和。

我们假定我们已经枚举了一个终点。发现如果我们连接一条虚空的没有边权值的边到定的起点,那我们就等价于构造最小的欧拉回路。

现在我们要利用无向图的优美性质:无论如何,无向图中的所有点的度数和一定是偶数。
同时,根据题目的性质,发现将一条连接两个端点的边拆开成为以此连接相邻两个点的边后权值和不变同时能够连接更多的点因此一定不劣。

于是我们可以通过这样一种方法将原图补成一个欧拉回路:
在序列上从左向右扫,如果一个点的度数是奇数,令其向下一个点连接一条边。可以证明这样连接的点权一定不劣同时最终连出来的图上的所有点的度数都是偶数。具体而言可以根据上面的性质考虑。

看起来这张图已经是欧拉回路了。但是还有一个容易遗忘的问题:图不一定连通。由于根据题意每一个度数不为 0 的点都需要被遍历,因此我们又需要连接尽量短的边使得整张图连通。注意到这个东西与求连通块之间的最小生成树等价。由于是回路,因此这里连边的代价是最小生成树的权值的两倍。
由于一定是在相邻的连通块之间连边,因此最小生成树可能的边数是线性的。

发现不同终点的差别只在起点与终点连的那一条边上,因此考虑预处理之后再对于每一种终点处理答案。

时间复杂度 $O((n^2+m)) \lambda $,其中 $\lambda $ 是并查集的复杂度。容易想到并查集维护连通性。正确性可以结合代码理解。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=4e6+7,inf=1e9+7;
int n,m,s,fa[N],du[N],idcnt=0,res=0,bel[N],tdu[N];
struct node{
	int u,v;
}q[N];
struct edge{
	int u,v,w;
}a[N];
bool cmp(edge x,edge y){return x.w<y.w;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y){x=find(x),y=find(y);if(x!=y)fa[x]=y;}
void init(){
	for(int i=1;i<=n;i++)fa[i]=i,du[i]=0;
	for(int i=1;i<=m;i++){
		merge(q[i].u,q[i].v);
		res+=q[i].v-q[i].u;
		tdu[q[i].u]++,tdu[q[i].v]++;
	}
	for(int i=1;i<=n;i++)bel[i]=fa[i];
}
void solve(int t){
	int ans=0;for(int i=1;i<=n;i++)fa[i]=bel[i],du[i]=tdu[i];
	du[s]++,du[t]++;merge(s,t);
	for(int i=1;i<n;i++){
		if(du[i]&1){
			merge(i,i+1);ans++,du[i+1]++;
		}
	}
	idcnt=0;int lst=0;
	for(int i=1;i<=n;i++){
		if(du[i]) {
			if(lst&&find(i)!=find(lst)) a[++idcnt]={i,lst,abs(lst-i)};
			lst=i;
		}
	}
	sort(a+1,a+idcnt+1,cmp);
	for(int i=1;i<=idcnt;i++){
		int u=find(a[i].u),v=find(a[i].v);
		if(u!=v)merge(u,v),ans+=2*a[i].w;
	}
	cout<<res+ans<<' ';return;
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>s;
	for(int i=1,u,v;i<=m;i++){cin>>q[i].u>>q[i].v;if(q[i].u>q[i].v)swap(q[i].u,q[i].v);}
	init();
	for(int i=1;i<=n;i++)solve(i);
	return 0; 
}

P6168 [IOI 2016] railroad

另外一道略强的应用。

首先发现速度 \(\le s_i\) 的限制是奇怪的,这个东西不太好在图上表示,要么只能优化建图,但是注意到优化建图更适合处理连通性问题,因此考虑将这个限制转化。

发现如果我们要求进入某条轨道时的速度恰好为 \(s_i\),那么题目的限制等价于加速没有代价
于是我们考虑设立一个超级轨道,其进入的速度恰好为 \(+\infty\),出去的速度恰好为 \(1\)。发现我们可以将这个超级轨道看作起点和终点,因为从这个轨道出发与返回这个轨道的代价都一定是 \(0\)

我们将速度离散化,然后将轨道看作有向边,我们可以将速度从一个速度没有代价的变成另一个速度。由于超级轨道的存在(\(+\infty\)\(1\) 连边),因此我们可以与上一道题类似的要求构造一个欧拉回路使得包含所有的边(轨道)同时代价最小。

由于轨道是有向边,没有无向图的度数那样优美的性质。但是我们发现由于从小向大连的边没有代价,因此我们可以将任何从大向小连的边无痛变成回路。
现在的问题是如果有孤单的轨道是从小连向大的,那么我们就必须用从大连向小的边(有代价)来使得其构成一个回路。

注意到我们可以通过差分来求一个点需要向前连多少条边使得图成为一个欧拉回路。

然后我们同样需要将图连通。同样使用最小生成树来做。注意到由于从小到大的边是没有代价的,因此使得图连通的额外的代价即为最小生成树的边权值。

复杂度 \(O(n\log n+n\lambda)\),其中 $\lambda $ 是并查集的复杂度。注意到复杂度阈值是离散化。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=6e5+7,inf=1e9+7;
int n,m,tmp[N],cnt=0,idcnt=0,fa[N],du[N],ans=0,s[N],t[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y){
	x=find(x),y=find(y);
	if(x!=y)fa[x]=y;
}
struct node{int u,v,w;}a[N];
bool cmp(node x,node y){return x.w<y.w;}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;for(int i=1;i<=n;i++)cin>>s[i]>>t[i],tmp[++cnt]=s[i],tmp[++cnt]=t[i];
	n++;s[n]=tmp[++cnt]=inf,t[n]=tmp[++cnt]=1;
	sort(tmp+1,tmp+cnt+1);cnt=unique(tmp+1,tmp+cnt+1)-(tmp+1);
	for(int i=1;i<=cnt;i++)fa[i]=i;
	for(int i=1;i<=n;i++){
		s[i]=lower_bound(tmp+1,tmp+cnt+1,s[i])-tmp;
		t[i]=lower_bound(tmp+1,tmp+cnt+1,t[i])-tmp;
		merge(s[i],t[i]);du[s[i]]++,du[t[i]]--;
	}
	for(int i=1;i<=cnt;i++)du[i]+=du[i-1];	
	for(int i=1;i<cnt;i++){
		if(du[i]!=0){
			merge(i,i+1);
			if(du[i]>0)ans+=(tmp[i+1]-tmp[i])*du[i];
		}
	}
	for(int i=1;i<cnt;i++){if(find(i)!=find(i+1)) a[++idcnt]={i,i+1,tmp[i+1]-tmp[i]};}
	sort(a+1,a+idcnt+1,cmp);
	for(int i=1;i<=idcnt;i++){
		int u=find(a[i].u),v=find(a[i].v);
		if(u!=v)merge(u,v),ans+=a[i].w;
	}
	cout<<ans;return 0;
}
posted @ 2025-07-07 21:51  all_for_god  阅读(26)  评论(1)    收藏  举报