欧拉路与欧拉图 学习笔记
欧拉图的相关概念
设
- 如果
G 中存在一条包含所有边的简单回路,那么我们称此回路为欧拉回路(Euler circuit),G 为欧拉图(Euler graph)或 E 图。 - 如果
G 中存在一条包含所有边的简单路,那么我们称此回路为欧拉路(Euler path),G 为半欧拉图(semi Euler graph)。 - 特别地,我们定义一张平凡图(trivial graph)(只由一个孤立结点组成的图)为欧拉图。(不清楚对于一般的零图是否有此结论)
注意,半欧拉图中不包含欧拉回路。
结合一笔画问题,从任意一个点开始都可以一笔画完的图是欧拉图,必须从某个点开始才能一笔画完的图是半欧拉图。
要注意区分欧拉图与哈密尔顿图(Hamilton graph)的差异:
哈密尔顿回路指从一个点出发,不重不漏地经过每个点的回路,而欧拉回路的定义中是边。
欧拉图的基本性质
- 性质:一张无向连通图
G 是非平凡的欧拉图,当且仅当它可被表示为一些环的并。
证明:把欧拉回路分段,使每一段都是简单环。必要性同理。
- 性质:一张无向连通图
G 是欧拉图,当且仅当图中每个结点的度数均为偶数。(常用于欧拉图的判定,下同)
证明:(必要性)设
我们发现,
那么,对所有的点
(充分性)我们任取一个度数不为
由于
设
由于边数是有限的,因此这个过程一定会停止。停止的唯一可能是在
删去这条回路和当前度数为
若
由于
重复以上 找环-并入-删边 的过程,每次图的规模都会缩小,最终一定会得到一个空图。此时的回路便是包含了所有边的简单回路。
这便说明了
- 性质:一张有向图
G 是欧拉图,当且仅当其基图连通,且图中每个结点的出度等于入度。
证明与上一性质类似,此处略去。
- 性质:一张无向连通图
G 是半欧拉图,当且仅当图中有且仅有两个点的度数为奇数。(常用于半欧拉图的判定,下同)
证明:(必要性)设
我们发现,
(特别地,
那么,对所有的点
如果
(充分性)由于
删去这条路,图中每个点的度数都是偶数。由上文第二条性质可知,每个连通块内都存在一条欧拉回路。
依次将其并入原有的通路,便得到了一条
- 性质:一张有向图
G 是半欧拉图,当且仅当其基图连通,且图中存在两点u,v 满足d^+(u)=d^-(u)+1,d^-(v)=d^+(v)+1 ,其余每个结点的出度等于入度。
证明与上一性质类似,此处略去。
- 性质:若无向连通图
G 的奇点(度为奇数的点)数为k ,至少需要用\dfrac{k}{2} 笔画成。
证明:这其实是上面一些性质的扩展,而且我们由握手原理易知
证明的方法其实类似上面,此处不作展开。
- 性质:可以用加边的方式把一个非欧拉图变成欧拉图。
具体地,对含有
对基图连通的有向图,至少要加的边数应为:
Fleury 算法
Fleuly 算法(弗洛莱算法)又称避桥法,是用于求欧拉路或欧拉回路的算法。
算法的主要流程是每次选择下一条边的过程中优先选择一条不为桥的边。(因为桥意味着走过去就再也走不回来了)
由于每次删一条边都要再跑一遍 Tarjan,因此复杂度是
可以依赖动态图进行一些优化,不过实践意义不大。
Hierholzer 算法
Hierholzer 算法又称逐步插入回路法,也是用于求欧拉路或欧拉回路的算法。
算法的流程与性质中的证明类似,具体地:
- 我们先在图中任意找出一条回路。
- 任取一条目前回路中的点,将其替换为一条简单回路。这可以采用递归的形式进行
- 以此类推,可以找到一条欧拉回路。
如果从路开始的话,就可以找到一条欧拉路。
但是,这样暴力实现的复杂度还是用于
我们发现,引起复杂度退化的一个主要原因是我们在访问到一个点时,会多次访问那些已经经过的边。
那么,我们只要在搜索时直接把经过的边删去即可做到
(蓝书上的做法,一般不用)由于递归深度是
例题
- P2731 [USACO3.3]骑马修栅栏 Riding the Fences
这是无向图的板子。由于数据范围极小,因此可以采用邻接矩阵配合各种暴力。
还有,这题有重边。
void dfs(int x)
{
for(int y=1;y<=n;y++)
if(g[x][y]>=1) {
g[x][y]--, g[y][x]--;
dfs(y);
}
ans[++cnt]=x;
}
- P7771 【模板】欧拉路径
这是有向图的板子。由于邻接表的特性,倒着加边可以帮助我们取到字典序最小。
void dfs(int x)
{
while(true)
{
int i=head[x];
while(i&&vis[i]) i=Next[i];
head[x]=i;
if(i==0) break;
vis[i]=true, dfs(ver[i]);
}
ans[++cnt]=x;
}
- UVA10735 混合图的欧拉回路 Euler Circuit
这题包着个欧拉回路的外皮,实际上没法用 Hierholzer 算法做,需要跑网络流。
这说明,Hierholzer 算法只能在单纯的有向图和无向图中使用,无法在混合图上跑。
- P3520 [POI2011]SMI-Garbage
我们发现只有初始权值和最终权值不同的需要一次修改,别的都不需要。
那么我们只需要把这些边全都加入图中,对每个连通块跑一遍 Hierholzer 即可。
输出有点麻烦,需要用一个栈去维护。具体看代码。
(LOJ 上的数据比这里强一万倍,那上面就是蓝书中提到的 DFS 爆栈题)
void solve(int rt)
{
R[cur=1]=rt;
while(cur) {
int x=R[cur], i=head[x];
while(i&&vis[i]) i=Next[i];
head[x]=i;
if(i==0) {
if(ins[x]) {
res.clear();
res.push_back(x);
while(st[top]!=x) {
res.push_back(st[top]);
ins[st[top]]=false, top--;
}
res.push_back(st[top]), ins[st[top]]=false, top--;
v.push_back(res);
}
ins[x]=true, st[++top]=x;
cur--; continue;
}
vis[i]=vis[i^1]=true;
R[++cur]=ver[i];
}
}
Code
- #10110. 「一本通 3.7 练习 4」太鼓达人
这是一道经典题。
我们把每个
比如,
- 由于
\texttt{00} 可以变为\texttt{000},\texttt{001} ,因此我们从\texttt{00} 出发向\texttt{00} 、\texttt{01} 各连一条有向边; - 由于
\texttt{01} 可以变为\texttt{010},\texttt{011} ,因此我们从\texttt{01} 出发向\texttt{10} 、\texttt{11} 各连一条有向边; - 由于
\texttt{10} 可以变为\texttt{100},\texttt{101} ,因此我们从\texttt{10} 出发向\texttt{00} 、\texttt{01} 各连一条有向边; - 由于
\texttt{11} 可以变为\texttt{110},\texttt{111} ,因此我们从\texttt{11} 出发向\texttt{10} 、\texttt{11} 各连一条有向边。
然后我们要求每种转移(就是代表着连续
void dfs(int x)
{
if(g[0][x]!=-1) {
int tmp=g[0][x];
g[0][x]=-1;
dfs(tmp);
}
if(g[1][x]!=-1) {
int tmp=g[1][x];
g[1][x]=-1;
dfs(tmp);
}
ans[++cnt]=x;
}
int main()
{
cin>>k;
n=(1<<(k-1))-1;
fo(i,0,n) fo(j,0,1) g[j][i]=((i<<1|j)&n);
dfs(0);
printf("%d ",(1<<k));
int cur=k-2;
fo(i,1,cur) putchar('0');
while(cur<(1<<k)) {
printf("%d",ans[cnt]&1);
cnt--, cur++;
}
puts("");
return 0;
}