若无特殊说明,以下所有图均指连通图。
定义
欧拉路径,欧拉回路,欧拉图
对于一个图,如果存在一条路径恰好经过所有边一次,则称这条路径为一条欧拉路径。
如果存在一条回路经过所有边恰好一次,则称这条路径为一条欧拉回路。
存在欧拉回路的图被称为欧拉图。
环分解
如果一个图的边集可以被分成若干个简单环,则称这个图可以环分解。
判定
判定定理1:一个图存在欧拉回路当且仅当这个图可以被环分解。
- 充分性:如果可以环分解,那么每次选取两个相交的环合并成一个大环即可。
- 必要性:对于一条欧拉回路,每次遇到重复点就把这段路径连成的环分解出去,这样一定能形成若干简单环。
判定定理2:一个图可以被环分解当且仅当所有点度数都为偶数。
- 充分性:每次选取一个简单环,删掉之后所有点度数依旧是偶数,归纳即可。
- 必要性:每个点进入次数和出去次数相等,因此都是偶数。
结合上面两条我们可以得出欧拉回路的判定定理:
判定定理3:一个图存在欧拉回路当且仅当所有点度数都是偶数。
并且我们可以知道,一个图存在欧拉回路、可以被环分解、所有点度数都是偶数,这是三个完全等价的条件。
对于有向图,类似的可以得出充要条件是所有节点入度等于出度。
还有下面的推论:
推论:一个图存在欧拉路径当且仅当恰好有两个结点的度数是奇数。
求法
Hierholzer 算法
维护当前的欧拉回路,每次把回路里的一个点拓展成一个环。
DFS 写法
核心思路是每次找环拼起来,实际上有一个简单的 DFS 做法:从任意一个节点开始,每次枚举当前点的出边,沿着第一个没有走的走过去,代码如下:
void dfs(int x)
{
for(int i=0;i<G[x].size();i++)if(!vis[G[x][i].id])
{
vis[G[x][i].id]=1;
int y=G[x][i].y;
dfs(y);
}
ans[++tot]=x;//回溯的时候再把点加进去
}
为什么要在回溯的时候才加点?
如图所示,我们以 \(1\) 号点为起点,沿着 1 - 5 - 4 - 3 - 2 -1 找到一个环,但是如果直接按照 DFS 访问顺序加点,另一个环就访问不到了;而在回溯时加点就可以避免这个问题,如果回溯过程中找到了新的环就可以直接加到欧拉回路里。
不过因为是回溯过程加的点,所以最后求的是倒着的一条欧拉回路,我们反过来输出即可。
对于欧拉路径,只要从度数为奇数的点开始 DFS 就行了。
当前弧优化
按照上面的代码写复杂度是不对的,因为每次都要重新枚举所有出边,不过显然可以记录一个变量表示当前遍历到每个节点的哪条出边,这样接着这个变量继续枚举即可,代码如下:
void dfs(int x)
{
for(int &i=cur[x];i<G[x].size();i++)if(!vis[G[x][i].id])
{
vis[G[x][i].id]=1;
int y=G[x][i].y;
dfs(y);
}
ans[++tot]=x;//回溯的时候再把点加进去
}
最小字典序欧拉回路
把每个节点的出边按照字典序排序即可。
基础题目
P7771 【模板】欧拉路径
[USACO3.3] 骑马修栅栏 Riding the Fences
上面两道都是模板题。
[ABC286G] Unique Walk
因为非关键边可以经过任意次,所以直接把非关键边连接的点缩成来,在剩下的图上跑欧拉回路即可。
BZOJ3706 反色刷
首先有解的条件显然是所有点连的黑色边数量都是偶数。
对于一个有至少一条黑色边的连通块,一定只需要一次操作就能全变白,因此答案就是有至少一条黑色边的连通块数量。
CF723E One-Way Reform
首先答案最多是度数为偶数的点的个数,并且我们可以达成这个上界:
新建一个虚点,从虚点向每个度数为奇数的点连边,然后跑一条欧拉回路,按照欧拉回路的方式定即可。
CF547D Mike and Fish
对行列建点,建成一个二分图,然后每个点在他对应的行列之间连边。
延续上一个题的构造就可以把每条边定向使得所有点的入度和出度差的绝对值不超过 \(1\)。
CF209C Trails and Glades
如果图连通,那么答案肯定是奇数点个数/2。
如果图不连通,那么对于一个没有奇数点的连通块,向外面连两条出边即可。
进阶题目
CF429E Points and Segments
如果区间是红色就给区间内的点都加一,否则都减一,最后要求所有点的绝对值不超过 \(1\)。
考虑如果所有点都偶数个区间覆盖时,我们这样构造:
对于每个区间,在 \(l,r+1\) 之间连无向边,然后跑欧拉回路,如果这条边从左指向右就让差分数组 \(d_l-1,d_{r+1}+1\),另一种类似。这样因为每个点入度出度相等,所以差分数组 \(d_i=0\),故所有点被两种区间覆盖的次数都相等,符合条件。
当有些点被覆盖奇数次时,类似之间加虚点的操作,我们对于每个被覆盖奇数次的位置 \(x\),加入一个区间 \([x,x]\) ,再跑上面的做法即可。
[IOI2016] railroad
题意就是,你在数轴上游走,向左走代价为 \(1\),向右走没有代价,还有 \(m\) 条有向特殊边必须经过恰好一次,问最优代价。
考虑最终你游走的过程,如果 \(i\to i+1\) 走了 \(c\) 次,就连 \(c\) 条 \(i\to i+1\) 的边,\(i+1\to i\) 同理,那么就是要求加最少代价的边使得存在欧拉路径。
路径是不太好处理的,注意到我们可以加一条 \(inf\) 到 \(1\) 的边不影响答案,因此可以变成询问欧拉回路。
对于相邻两条边 \(i\to i+1\),我们求出它被特殊边覆盖的次数(从右向左是-1,从左向右是+1) \(w\),如果 \(w>0\),我们需要再加入 \(w\) 条向 \(i+1\to i\) 的边,代价为 \(w\),否则假如 \(i\to i+1\) 的边,不需要代价。
这样加边显然是最优的,并且满足了欧拉回路的一个限制:所有点度数都是偶数。
但是还要联通才能有欧拉回路,于是我们还要加一些边使得图联通,显然只会加在相邻两点之间,一去一来。然后我们把这些边跑一个最小生成树使得图联通即可。
[省选联考 2020 B 卷] 丁香之路
和上题大致相同,只是向左向右都有代价了,没有什么本质区别。
CF325E The Red Button
首先容易证明 \(n\) 为奇数无解,否则令 \(m=n/2\) 。
关键性质,\(x,x+m\) 拥有相同的入边,出边集合,其中 \(x\in [0,m)\) 。也就是说这对点某种程度上可以看成一个点
那么我们建一个 \(m\) 个点的图,每个点实际代表一对点 \([0,m)\) 。然后从 \(x\) 向 \(2x\bmod m,2x+1\bmod m\) 连边跑欧拉回路,正确性证明比较显然。