欧拉图
定义
欧拉回路:通过图中每条边恰好一次的回路(起点与终点在同一个点)。
欧拉路径:通过图中每条边恰好一次的通路。
欧拉图:具有欧拉回路的图。
半欧拉图:具有欧拉路径但不具有欧拉回路的图。
无向图的判定
连通无向图 \(G\) 有欧拉路径的充要条件为:
- \(G\) 中奇点(度数为奇数的点)有 \(0\) 个或者 \(2\) 个。其中当奇点有 \(2\) 个时,欧拉路径的起点和终点分别为这两个点(半欧拉图)。有 \(0\) 个奇点时为欧拉图。
有向图的判定
底图连通的有向图 \(G\) 有欧拉路径的充要条件为:
-
\(G\) 的所有顶点入度和出度都相等。(欧拉图)
-
或者只有两个顶点的入度和出度不相等,且其中一个顶点的出度与入度之差为 \(1\),另一个顶点的入度与出度之差为 \(1\)。这时欧拉路径的起点为前者,终点为后者。(半欧拉图)
求有向图的欧拉路径
先考虑不存在欧拉路径的情况,我们需要统计每个点的入度和出度,然后判断上面定理的条件就行,如果发现不符合直接输出 No。
先确定起点,沿用我们刚刚用来判无解的入度和出度点,找到出度 \(-\) 入度为 \(1\) 的点,这个点就是起点。
接下来从起点开始搜索,然后选择一条没有走过的边走,继续搜索下一个点。每次返回的时候,将点入栈,最后的答案就是栈顶到栈底。
由于题目让我们求字典序最小的欧拉路径,为了保证字典序最小,我们每次走的时候尽量选择连向较小的结点那条边。又因为链式前向星存点的顺序与遍历的顺序相反,所以我们只需要把输入的所有边 u->v 按 \(v\) 从大到小排序再建图即可。
这个时候就会发现只能拿到 90pts,最后一个点会 TLE。我们被卡的地方就是因为只是用一个 bool 数组来标记一条边是否走过,这样每次还是要遍历所有的边,考虑最坏的情况,数据给出的是稠密图,每次要遍历很多条边,一个点又会走到很多次,时间复杂度能达到大约 \(O(n^2)\),就会被卡。所以我们考虑使用类似 dinic 中的当前弧优化来优化这一点。因为我们每次一定会取尽量小边,又因为一个点往外的边我们已经排好序,所以一个点被走的边的顺序是第 \(1\) 条、第 \(2\) 条、第 \(3\) 条……所以只需要在每次走过一条边的时候更新边的起点就好了嘛。这样一来我们就不用记录每条边有没有走过了,改变遍历的起点,每次遍历到的都是没走过的边。
第一次看链式前向星的当前弧优化是不太好理解的,所以可以先看看好理解的 vector 存图的当前弧优化:
for(int i=del[now];i<G[now].size();i=del[now])
{
del[now]=i+1;
dfs(G[now][i]);
}
实际上就是用了一个 \(del\) 数组来记录每个点遍历边的起点。
再来看看链式前向星的就好理解了:
for(int i=t[x];i;i=t[x])
{
t[x]=a[i].last;
dfs(a[i].id);
}
一开始我在 for 循环里面写了 i=a[i].last,这样是错误的,应该像上面一样是 i=t[x]。这两种我一开始看不出来什么区别,仔细想了下,我们在循环里面 dfs,可能在 dfs 的时候用掉了一些边,a[i].last 这条边就被用掉了,但是 t[x] 是实时更新的,一定是第一条没被走过的边。
讲一个我在学习这道题目一开始不理解的一个点:为什么要回溯时入栈,倒着输出,而不是边遍历边输出?
给一组 hack 数据:
3 4
1 2
2 1
2 3
3 2
后者跑出来的是 1 2 1 3 2,显然是错误的。
至于为什么,模拟一下可能就理解了。
边遍历边输出:
dfs(1):输出1 包含边2
dfs(2):输出2 包含边1 3
dfs(1):输出1 没有边
end
dfs(3):输出3 包含边2
dfs(2):输出2 没有边
end
end
end
end
回溯时入栈,倒着输出:
dfs(1):包含边2
dfs(2):包含边1 3
dfs(1):没有边
end s.push(1)
dfs(3):包含边2
dfs(2):没有边
end s.push(2)
end s.push(3)
end s.push(2)
end s.push(1)
问题在于「没有边」。
如果没有边,说明这个点已经遍历完毕,应该在路径的末尾输出,但 dfs 时会先进入这个点,告知程序这个点是路径末端的。但如果采用第一种代码的写法,这个点会直接计入路径并输出,这并不是我们想要的结果。
因此,路径末端的点会首先被找到,最先结束递归,并入栈,表明在结束 dfs 时入栈就是正确的。
最后贴上这题的完整代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+100,M=2e5+100;
int n,m,k,t[N],d1[N],d2[N],st,cnt1,cnt2,s[N],sk;
bool flag[M];
struct edge
{
int u,v;
}e[M];
struct node
{
int id,last;
}a[M];
bool cmp(edge a1,edge a2)
{
return a1.v>a2.v;
}
void add(int a1,int a2)
{
a[++k].id=a2;
a[k].last=t[a1];
t[a1]=k;
}
void dfs(int x)
{
//printf("%d ",x);
for(int i=t[x];i;i=t[x])
{
t[x]=a[i].last;
dfs(a[i].id);
}
s[++sk]=x;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&e[i].u,&e[i].v);
d1[e[i].v]++,d2[e[i].u]++;
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++) add(e[i].u,e[i].v);
for(int i=1;i<=n;i++)
{
if(d1[i]==d2[i]) continue;
if(d2[i]-d1[i]==1) cnt1++,st=i;
else if(d1[i]-d2[i]==1) cnt2++;
else
{
printf("No");
return 0;
}
}
if(!(!cnt1&&!cnt2||cnt1==1&&cnt2==1))
{
printf("No");
return 0;
}
if(!st) st=1;
dfs(st);
for(int i=sk;i;i--) printf("%d ",s[i]);
return 0;
}
时间复杂度 \(O(m+mlogm)\)。

浙公网安备 33010602011771号