图论(2) 欧拉图(一笔画问题)

基本概念

仅讨论有限图。

在图论中:

欧拉路径:是经过图中每条边恰好一次的路径

欧拉回路:是经过图中每条边恰好一次的回路。

欧拉图:图中存在欧拉回路

半欧拉图:图中不存在欧拉回路但是存在欧拉路径

性质

假设图 \(G\) 中不存在孤点,则连通图 \(G\) 存在如下三个等价性质:

  1. \(G\) 是欧拉图

  2. \(G\) 中所有顶点的度数都是偶数(对于有向图,每个顶点的入度等于出度)

  3. \(G\) 可被分解为若干条不共边回路的并集

Hierholzer 算法

思路

这一算法的核心思路为上述性质 \(3\) 。由性质 \(3\) 可得思路,我们可以找出图中不共边环后合并。

算法规则

  1. 从图中任意一个点开始找出一个环,并把这个环上所有边在原图中删除。(保证不会重复走边)

  2. 如果还有未走过的边,从闭环中的出度不为 \(0\) 一个顶点出发,并将走环状态回退到这个顶点(可以用栈存储走完此环后的所走的点)。

  3. 重复步骤 \(2\) 知道走完所有边。

特别要提醒的是,为了不让双向边被两个点重复走,我们可以用 \(map\) 来记录边(也可以对边编号,用一维 \(vis\) 解决,但由于将边排序时间复杂度已经带 \(log\) 故此优化用处不大),以此优化时间和空间。

Code.
#include<bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value 
const int inf=2147483647;
const int mod=1e9+7;
vector<int>g[2005];
int s[2005],cnt[2005],top=1;
map<pair<int,int>,int>mp;


//function 
void solve(){
	
	
	
	return;
}
void Euler(int u){
	//如果Euler提前走到的终点,则会直接加入答案序列
	//如果最后走到终点,则因为欧拉路定义其余边必然都被走过
	//故dfs正确性得证
	for(auto i:g[u]){
		if(mp[make_pair(u,i)]>0){
			mp[make_pair(u,i)]--;
			mp[make_pair(i,u)]--;
			Euler(i);
		}
	}
	s[++top]=u;
}


 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0); 
	
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	
	int m,st=500,n=0;
	cin>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		g[x].push_back(y);
		g[y].push_back(x);
		n=max(n,max(x,y));
		st=min(st,min(x,y));
		cnt[x]++;
		cnt[y]++;
		mp[make_pair(x,y)]++;
		mp[make_pair(y,x)]++;
	}
	for(int i=1;i<=n;i++){
		//欧拉路起点找法,由于性质二,寻找欧拉回路起点可为任意一点
		if(cnt[i]%2==1){
			st=i;
			break;
		}
	}
	for(int i=1;i<=n;i++){
		sort(g[i].begin(),g[i].end());
	}
	
	Euler(st);
	
	while(top>1)cout<<s[top--]<<endl;

	
	
	return 0;
}


例题

CF2110E Melody

题目描述

在 2077 年,统治世界的机器人意识到人类的音乐并不那么出色,于是它们开始创作自己的音乐。
为了创作音乐,机器人拥有一种特殊的乐器,能够产生 \(n\) 种不同的声音。每种声音由其音量和音高来表征。一系列声音被称为音乐。如果任意两个连续的声音仅在音量或仅在音高上有所不同,则该音乐被认为是优美的。如果任意三个连续的声音在音量或音高上相同,则该音乐被认为是单调的。
你需要创作一段优美且不单调的音乐,其中包含乐器产生的每个声音恰好一次。
\(1 \le t \le 10^4\)\(1 \le n \le 2 \cdot 10^5\)\(1 \le v_i, \space p_i \le 10^9\)
保证在所有 \(n\) 个声音中不存在重复,即对于任意 \(i \neq j\),至少满足 \(v_i \neq v_j\)\(p_i \neq p_j\) 中的一个条件。
所有测试用例的 \(n\) 之和不超过 \(2 \cdot 10^5\)

题目思路

首先思考我们确定下一位的前提是上一位有音量或音高与他相等,则我们可以想办法让相同音量或相同音高的音符产生一些联系。在看到“三个连续的声音在音量或音高上相同”,则必然存在如果这个音符与上个音符在音量(音高)上相同,则下个音符必然音高(音量)与该音符相同。

则我们可以将音量和音高看作点,音符看作边,相同音量或相同音高联系即为点重合,使用音符可以视作走边。

将整个问题建图解决后,容易发现原问题即为寻找欧拉路的模板题。

代码思路

由于我们将音量和音高做点,我们需要对他们进行离散化建点,建点后跑欧拉路即可。由于我们最后需要的是音符顺序,即欧拉路径上的边,我们可以对每个边编号,在建图和 \(map\) 记录边的数量时,我们将边的编号一同存进去,在跑欧拉路时用栈记录边即可。

Code.
#include<bits/stdc++.h>
#define endl "\n"
#define pb push_back
#define mkp make_pair
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value 
const int inf=2147483647;
const int mod=1e9+7;
struct node{
	int v,p,v1,p1,id;
}a[200005];
int cnt[400005],s[400005],top;
vector<pair<int,int> >e[400005];
map<pair<pair<int,int>,int>,int>mp;


//function 
bool cmp1(node a,node b){
	return a.v<b.v;
}
bool cmp2(node a,node b){
	return a.p<b.p;
}
bool cmp3(node a,node b){
	return a.id<b.id;
}
void Euler(int u){
	for(auto i:e[u]){
		if(mp[mkp(mkp(u,i.first),i.second)]>0){
			mp[mkp(mkp(u,i.first),i.second)]--;
			mp[mkp(mkp(i.first,u),i.second)]--;
			Euler(i.first);
			s[++top]=i.second;
		}
	}
}
void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i].v>>a[i].p;
	for(int i=1;i<=n;i++)a[i].id=i;
	//离散化
	sort(a+1,a+1+n,cmp1);
	int tot=0;
	for(int i=1;i<=n;i++){
		if(a[i].v==a[i-1].v)a[i].v1=tot;
		else a[i].v1=(++tot);
	}
	sort(a+1,a+1+n,cmp2);
	for(int i=1;i<=n;i++){
		if(a[i].p==a[i-1].p)a[i].p1=tot;
		else a[i].p1=(++tot);
	}
	sort(a+1,a+1+n,cmp3);
	for(int i=1;i<=tot;i++)e[i].clear();
	mp.clear();
	/*
	for(int i=1;i<=n;i++){
		cout<<a[i].v1<<' '<<a[i].p1<<endl;
	}
	*/
	//建图
	for(int i=1;i<=tot;i++)cnt[i]=0;
	for(int i=1;i<=n;i++){
		int u=a[i].v1,v=a[i].p1;
		e[u].pb(mkp(v,a[i].id));
		e[v].pb(mkp(u,a[i].id));
		mp[mkp(mkp(u,v),a[i].id)]++;
		mp[mkp(mkp(v,u),a[i].id)]++;
		cnt[u]++;
		cnt[v]++;
	}
	//找欧拉路起始点
	int st=1,tmp=0;
	for(int i=1;i<=tot;i++){
		if(cnt[i]%2==1){
			st=i;
			break;
		}
	}
	for(int i=1;i<=tot;i++){
		if(cnt[i]%2==1)tmp++;
	}

//	cout<<st<<endl;
	//跑欧拉路
	if(tmp>2)cout<<"No"<<endl;
	else {
		top=0;
		Euler(st);
		if(top==n){
			cout<<"Yes"<<endl;
			for(int i=n;i>0;i--)cout<<s[i]<<' ';
			cout<<endl;
		}
		else cout<<"No"<<endl;
	}
	
	
	
	return;
}


 
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0); 
	
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	
	int t;
	cin>>t;
	while(t--)solve();
	
	
	
	return 0;
}


代码其实没过,但是对拍了没出错,时间复杂度也自认没错,就这样了

posted @ 2025-05-02 20:55  -Delete  阅读(100)  评论(0)    收藏  举报