图论(2) 欧拉图(一笔画问题)
基本概念
仅讨论有限图。
在图论中:
欧拉路径:是经过图中每条边恰好一次的路径
欧拉回路:是经过图中每条边恰好一次的回路。
欧拉图:图中存在欧拉回路
半欧拉图:图中不存在欧拉回路但是存在欧拉路径
性质
假设图 \(G\) 中不存在孤点,则连通图 \(G\) 存在如下三个等价性质:
-
\(G\) 是欧拉图
-
\(G\) 中所有顶点的度数都是偶数(对于有向图,每个顶点的入度等于出度)
-
\(G\) 可被分解为若干条不共边回路的并集
Hierholzer 算法
思路
这一算法的核心思路为上述性质 \(3\) 。由性质 \(3\) 可得思路,我们可以找出图中不共边环后合并。
算法规则
-
从图中任意一个点开始找出一个环,并把这个环上所有边在原图中删除。(保证不会重复走边)
-
如果还有未走过的边,从闭环中的出度不为 \(0\) 一个顶点出发,并将走环状态回退到这个顶点(可以用栈存储走完此环后的所走的点)。
-
重复步骤 \(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;
}
代码其实没过,但是对拍了没出错,时间复杂度也自认没错,就这样了

浙公网安备 33010602011771号