欧拉回路(输出路径) - 欧拉回路 - AcWing 1184
给定一张图,请你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次。
输入格式
第一行包含一个整数 t,t∈{1,2},如果 t=1,表示所给图为无向图,如果 t=2,表示所给图为有向图。
第二行包含两个整数 n,m,表示图的结点数和边数。
接下来 m 行中,第 i 行两个整数 vi,ui,表示第 i 条边(从 1 开始编号)。
如果 t=1 则表示 vi 到 ui 有一条无向边。
如果 t=2 则表示 vi 到 ui 有一条有向边。
图中可能有重边也可能有自环。
点的编号从 1 到 n。
输出格式
如果无法一笔画出欧拉回路,则输出一行:NO。
否则,输出一行:YES,接下来一行输出 任意一组 合法方案即可。
如果 t=1,输出 m 个整数 p1,p2,…,pm。令 e=|pi|,那么 e 表示经过的第 i 条边的编号。如果 pi 为正数表示从 ve 走到 ue,否则表示从 ue 走到 ve。
如果 t=2,输出 m 个整数 p1,p2,…,pm。其中 pi 表示经过的第 i 条边的编号。
数据范围
1 ≤ n ≤ 1 0 5 , 0 ≤ m ≤ 2 × 1 0 5 1≤n≤10^5, 0≤m≤2×10^5 1≤n≤105,0≤m≤2×105
输入样例1:
1
3 3
1 2
2 3
1 3
输出样例1:
YES
1 2 -3
输入样例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、对于无向图:
① 、 欧 拉 路 径 : 度 数 为 奇 数 的 点 只 能 有 0 或 2 个 。 ①、欧拉路径:度数为奇数的点只能有0或2个。 ①、欧拉路径:度数为奇数的点只能有0或2个。
② 、 欧 拉 回 路 : 度 数 为 奇 数 的 点 只 能 有 0 个 。 ②、欧拉回路:度数为奇数的点只能有0个。 ②、欧拉回路:度数为奇数的点只能有0个。
2、对于有向图:
① 、 欧 拉 路 径 : 所 有 点 的 出 度 等 于 入 度 , 或 者 , 一 个 点 的 出 度 比 入 度 多 1 ( 起 点 ) , 一 个 点 的 入 度 比 出 度 多 1 ( 终 点 ) , 其 他 点 的 入 度 与 出 度 相 等 。 ①、欧拉路径:所有点的出度等于入度,或者,\\\qquad一个点的出度比入度多1(起点),一个点的入度比出度多1(终点),其他点的入度与出度相等。 ①、欧拉路径:所有点的出度等于入度,或者,一个点的出度比入度多1(起点),一个点的入度比出度多1(终点),其他点的入度与出度相等。
② 、 欧 拉 回 路 : 所 有 点 的 出 度 等 于 入 度 。 ②、欧拉回路:所有点的出度等于入度。 ②、欧拉回路:所有点的出度等于入度。
遍历欧拉回路:
从 出 度 不 为 0 的 点 开 始 进 入 搜 索 。 从出度不为0的点开始进入搜索。 从出度不为0的点开始进入搜索。
注 意 , 先 深 搜 到 底 , 再 将 点 加 入 队 列 。 注意,先深搜到底,再将点加入队列。 注意,先深搜到底,再将点加入队列。
最 后 倒 序 输 出 队 列 , 得 到 的 欧 拉 路 径 。 最后倒序输出队列,得到的欧拉路径。 最后倒序输出队列,得到的欧拉路径。
优化:
由 于 每 条 边 仅 需 遍 历 一 次 , 为 了 避 免 很 多 自 环 出 现 在 同 一 个 点 上 的 情 况 , 由于每条边仅需遍历一次,为了避免很多自环出现在同一个点上的情况, 由于每条边仅需遍历一次,为了避免很多自环出现在同一个点上的情况,
我 们 每 遍 历 一 条 边 就 跳 过 一 条 边 ( 删 除 ) , 体 现 在 将 邻 接 表 表 头 后 移 , 我们每遍历一条边就跳过一条边(删除),体现在将邻接表表头后移, 我们每遍历一条边就跳过一条边(删除),体现在将邻接表表头后移,
另 外 , 无 向 图 建 图 时 , 每 组 边 的 编 号 依 次 为 ( 0 , 1 ) 、 ( 2 , 3 ) 、 . . . ( 2 × m − 2 , 2 × m − 1 ) , 另外,无向图建图时,每组边的编号依次为(0,1)、(2,3)、...(2×m-2,2×m-1), 另外,无向图建图时,每组边的编号依次为(0,1)、(2,3)、...(2×m−2,2×m−1),
为 了 删 除 某 条 边 的 反 向 边 , 可 以 对 该 边 的 编 号 异 或 1 , 如 0 ⨁ 1 = 1 , 2 ⨁ 1 = 3 。 为了删除某条边的反向边,可以对该边的编号异或1,如0\bigoplus1=1,2\bigoplus1=3。 为了删除某条边的反向边,可以对该边的编号异或1,如0⨁1=1,2⨁1=3。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010, M=400010;
int t,n,m;
int e[M],ne[M],h[N],idx;
int ans[M/2],cnt;
int din[N],dout[N];
bool st[M];
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;) //因为要求欧拉回路,我们从一个点进入就能够遍历到所有边,
{ //每次都从h[u]所指向的边继续向下搜索,每次都删除一条边
if(st[i])
{
i=ne[i]; //跳过该边(删除)
continue;
}
st[i]=true;
if(t==1) st[i^1]=true; //无向边
int id;
if(t==1)
{
id=i/2+1;
if(i & 1) id=-id;
}
else id=i+1;
int j=e[i];
i=ne[i]; //先跳过该边(删除),再进入搜索
dfs(j);
ans[++cnt]=id; //先搜索再记录答案,因为要一笔画
}
}
int main()
{
scanf("%d%d%d",&t,&n,&m);
memset(h,-1,sizeof h);
int a,b;
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
if(t==1) add(b,a);
din[b]++,dout[a]++;
}
if(t==1)
{
for(int i=1;i<=n;i++)
if(din[i]+dout[i] & 1)
{
puts("NO");
return 0;
}
}
else
{
for(int i=1;i<=n;i++)
if(din[i]!=dout[i])
{
puts("NO");
return 0;
}
}
for(int i=1;i<=n;i++) //从一个非孤立点进入搜索
if(h[i]!=-1)
{
dfs(i);
break;
}
if(cnt<m)
{
puts("NO");
return 0;
}
puts("YES");
for(int i=cnt;i;i--) printf("%d ",ans[i]); //倒序输出
puts("");
return 0;
}