浅谈欧拉路径,欧拉回路
引入
前有哈密顿路径,表示经过每个点恰好一次,现有欧拉路径,表示经过每条边恰好一次。
许多题目重要的是建模,往往最浅的建模就是点之间的连边,表示可以到达。如果说需要满足到达每个点一次,这就变成了 N P C \tt NPC NPC 问题。但是我们往往可以将一个信息拆分成若干个信息,变成边之间的关系,这样就有多项式复杂度的解法,同样这个是可以求方案的。
本篇会向读者介绍欧拉路径和欧拉回路以及其判定方法,还有常用的套路。
欧拉图
具有欧拉回路的图叫欧拉图,如果只有欧拉路径就叫做半欧拉图。
欧拉路径
定义:
经过每条边恰好一次,起点和终点不一定相同。
无向图
每个点的度数都是偶数,或者只有两个节点度数是奇数。
有向图
设一个点的权值 v i v_i vi 表示其出度减去入度。
那么存在的欧拉路径的条件是 v i = 0 v_i = 0 vi=0 或者同时存在一个 v i = 1 , v j = − 1 , i ≠ j v_i = 1, v_j = -1, i \ne j vi=1,vj=−1,i=j。
欧拉回路
定义
经过每条边恰好一次,起点和终点相同。
无向图
每个点的度数都是偶数。
有向图
设一个点的权值 v i v_i vi 表示其出度减去入度。
那么存在的欧拉路径的条件是 v i = 0 v_i = 0 vi=0。
具体实现
如果说对于一条路径起点和终点是不同的,作为开始的节点需要是出度较大的节点。
我们考虑直接进行暴力 d f s \tt dfs dfs,每次删除一条边。在遍历完其相邻的所有点之后将当前点加入答案。
复杂度是 O ( n + m ) O(n + m) O(n+m) 的。
void dfs(int p) {
while(!vc[p].empty()) {
int v = vc[p].back(); vc[p].pop_back();
dfs(v);
}
ans[++ ed] = p;
}
为什么要最后加入当前点呢?
如果说出现环的情况,而且我们对于当前点 u u u 并不是第一次遍历环,那么直接记录答案显然是不行的。
但是我们可以先走这个环,再走链。这种时候就可以考虑最后加入点,这样就是倒着走,环并不会影响答案。
读者可以尝试画画图,因为笔者个人博客
从来不放图,请见谅。
因为这样我们的答案是反着的,所以我们可以翻转一下数组。
之后的答案都指翻转数组之后的。
套路
字典序要求
- 字典序最小:贪心走最小的点即可。
- 字典序最大:贪心走最大的点即可。
拆点成边
例题1
求一个最短的字符串,使得其包含所有的 n n n 位 k k k 进制数。
最好的方法就是考虑每次增加一个字符,但是显然直接来做是不方便的。
可能会考虑对于每一个 k k k 进制数,其能到达的数连边加边权,边权是需要的字符数。
这样转换只能用 2 x 2^x 2x 指数级暴力,状压来写。
我们考虑将每个 n n n 位数,拆分成两个 n − 1 n - 1 n−1 位的数。 ( a 1 a 2 a 3 a 4 a 5 … a n − 1 ) k → ( a 2 a 3 a 4 a 5 a 6 … a n ) k (a_1a_2a_3a_4a_5\dots a_{n - 1})_k \to (a_2 a_3 a_4 a_5 a_6 \dots a_n)_k (a1a2a3a4a5…an−1)k→(a2a3a4a5a6…an)k 进行连边。
这样拆出来的每条边都需要经过恰好一次,每次正好是增加一个字符,可以保证答案最小。
如何保证有解?
显然对于最终的一个字符串肯定能拆分成若干个上述形式的 n − 1 n - 1 n−