欧拉路径和欧拉回路
概念
- 在一个图中,一条路径能将所有边都走且仅走一遍的路径,为欧拉路径
- 欧拉回路是特殊的欧拉路径,即起点等于终点
存在欧拉路径和欧拉回路的条件
- 在无向图中,所有点连通
- 存在欧拉路径:度数为奇数的点有0个或2个
- 存在欧拉回路:度数为奇数的点有0个
- 在有向图中,所有点连通
- 存在欧拉路径:要么所有点出度等于入度;要么除了两个点出度等于入度 剩余两个点:一个点出度比入度多1(起点),一个点入度比出度多1(终点)
- 存在欧拉回路:所有点出度等于入度
UVA10203 铲雪车
题意:
给定一张无向图,需要对边的两个方向铲两次雪,铲雪的速度和不铲的速度已知,问最少花多少时间
思路:
将无向边变成两条相反的有向边,所有点入度等于出度,存在欧拉回路,因此时间即为总路程*2/铲雪速度
UOJ117 欧拉回路(模板)
题意:
找无向图或者有向图的欧拉回路
优化:
- 欧拉路径是遍历边而不是点,每次要将边删去,复杂度才会是线性
- 改法:每次遍历后,让
h[u]=ne[i]其次是遍历改成i=h[u];~i;i=h[u],一直让它等于h[u],保证儿子能影响父亲
cpp
int e[M],ne[M],h[N],idx;
int din[N],dout[N];
bool used[M];
int n,m,ans[M],cnt,type;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
for(int i=h[u];~i;i=h[u]){
if(used[i]){
h[u]=ne[i];
continue;
}
used[i]=1;
if(type==1)used[i^1]=1;
h[u]=ne[i];
dfs(e[i]);
int t;
if(type==1){
t=i/2+1;
if(i&1)t=-t;
}
else t=i+1;
ans[++cnt]=t;
}
}
void solve(){
cin>>type>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
add(a,b);
din[b]++,dout[a]++;
if(type==1)add(b,a);
}
if(type==1){
for(int i=1;i<=n;i++){
if(din[i]+dout[i]&1){
cout<<"NO\n";
return;
}
}
}
else{
for(int i=1;i<=n;i++){
if(din[i]!=dout[i]){
cout<<"NO\n";
return;
}
}
}
for(int i=1;i<=n;i++){
if(h[i]!=-1){
dfs(i);
break;
}
}
if(cnt<m){x
cout<<"NO\n";
return;
}
cout<<"YES\n";
for(int i=cnt;i;i--)cout<<ans[i]<<" ";
}P7771 【模板】欧拉路径(有向图输出字典序最小)
思路:
- 坑点:字典序最大反过来找不会是字典序最小
- 欧拉路径是逆序输出的,先遍历的点在数组中位置越后,逆序后就越前
- 只需要按照当前字典序最小的点先走即可,逆序输出即为字典序最小
cpp
int n,m;
vector<int> adj[N];
int del[N],din[N],dout[N],ans[M],cnt;
void dfs(int u){
for(int i=del[u];i<adj[u].size();i=del[u]){
del[u]=i+1;
dfs(adj[u][i]);
ans[++cnt]=adj[u][i];
}
}
void solve(){
cin>>n>>m;
while(m--){
int u,v;
cin>>u>>v;
dout[u]++,din[v]++;
adj[u].push_back(v);
}
for(int i=1;i<=n;i++)sort(adj[i].begin(),adj[i].end());
bool in=0,out=0;
int start=1;
for(int i=1;i<=n;i++){
if(din[i]!=dout[i]){
if(din[i]==dout[i]+1){
if(in){
cout<<"No\n";
return;
}
in=1;
}
else if(dout[i]==din[i]+1){
if(out){
cout<<"No\n";
return;
}
out=1;
start=i;
}
else{
cout<<"No\n";
return;
}
}
}
if(in^out){
cout<<"No\n";
return;
}
dfs(start);
ans[++cnt]=start;
for(int i=cnt;i;i--)cout<<ans[i]<<" ";
}P2731 [USACO3.3] 骑马修栅栏 Riding the Fences(无向图输出字典序最小)
cpp
int m;
vector<PII> adj[N];
int del[N],ans[M],cnt,idx;
bool used[M];
void dfs(int u){
for(int i=del[u];i<adj[u].size();i=del[u]){
auto &[v,id]=adj[u][i];
del[u]=i+1;
if(used[id])continue;
used[id]=used[id^1]=true;
dfs(v);
ans[++cnt]=v;
}
}
void solve(){
cin>>m;
while(m--){
int u,v;
cin>>u>>v;
adj[u].emplace_back(v,idx++);
adj[v].emplace_back(u,idx++);
}
for(int i=1;i<=500;i++)sort(adj[i].begin(),adj[i].end());
bool g=0;
for(int i=1;i<=500;i++){
if(adj[i].size()%2){
dfs(i);
ans[++cnt]=i;
g=1;
break;
}
}
for(int i=1;i<=500;i++){
if(adj[i].size()&&!g){
dfs(i);
ans[++cnt]=i;
break;
}
}
for(int i=cnt;i;i--)cout<<ans[i]<<"\n";
}UVA10129 单词 Play on Words
题意:
- 两个单词的尾部和头部字母相同就能连接
- 给定多个单词,问能否拼接成在一起 思路:
- 题意即问有向图是否存在欧拉回路
- 并查集判断是否全部连通
- 要么全部入度等于出度,要么入度多一和出度多一的点各一个
P3520 [POI2011] SMI-Garbage(找出全部简单环)
题意:
n个点m条边,边有0和1两种状态,每走一次边就会改变一种状态
每次只能走环形路径并且除了起点外其他点只能经过一次 给定m条边的初始状态数和目标状态数
问是否存在方案
思路:
- 即找多个简单环,转化为欧拉路径
- 对于始末状态相同的点,可以拆成两条边,删去这两条边,度数减2,不影响欧拉回路的性质
- 即每个点入度等于出度
- 使用类似tarjan算法的方法判环
cpp
vector<PII> adj[N];
int idx,cnt,del[N],stk[M],top;
vector<int> ans[M];
bool used[M],in_stack[N];
void dfs(int u){
for(int i=del[u];i<adj[u].size();i=del[u]){
auto &[v,id]=adj[u][i];
del[u]=i+1;
if(used[id])continue;
used[id]=used[id^1]=true;
dfs(v);
}
if(in_stack[u]){
cnt++;
int y;
ans[cnt].push_back(u);
do{
y=stk[top--];
in_stack[y]=false;
ans[cnt].push_back(y);
}while(y!=u);
}
//即使在栈中弹出后,要重新加进来
in_stack[u]=true;
stk[++top]=u;
}
void solve(){
int n,m;
cin>>n>>m;
while(m--){
int a,b,s,t;
cin>>a>>b>>s>>t;
if(s==t)continue;
adj[a].emplace_back(b,idx++);
adj[b].emplace_back(a,idx++);
}
int c=0;
for(int i=1;i<=n;i++)sort(adj[i].begin(),adj[i].end()),c+=adj[i].size()%2;
if(c){
cout<<"NIE\n";
return;
}
for(int i=1;i<=n;i++)dfs(i);
cout<<cnt<<"\n";
for(int i=1;i<=cnt;i++){
cout<<ans[i].size()-1<<" ";
for(auto &x:ans[i])cout<<x<<" ";
cout<<"\n";
}
}P3443 [POI2006] LIS-The Postman
题意:
找欧拉回路,并且回路包含给定的序列
思路:
- 对于序列的一条边,不能有多个前驱后继,必须在原图存在
- 一种做法是将边全部删去,换成一条边,这样比较难调
- 另一种做法是打标记,只保留前面最前面的边,在dfs输出时如果有后缀边就一直按照这条路走即可