代码简单。
1.定义
即”一笔画“问题,从一个点出发,能不重复地走过所有边,则称该路径为欧拉路径。
如果能走回原点,则该路径为欧拉回路。
注意,点是可以多次经过的,只要不重复走边即可。
2.定理
前提是图连通。
1)无向图
一个图有欧拉回路的充要条件是:该图的每一个点的度数都为偶数。
欧拉路径的充要条件是:除了起始点和终点的度数为奇数,其余都为偶数,或者是欧拉回路。
即:度数为奇数的点只有 0 或 2 个。
必要性易证:如果原命题不成立,每一次经过一个点,需要消耗 2 个度,则有些点度数为奇数的点进来后就没有出去的边了,就没法构成欧拉路径了。
2)有向图
欧拉回路的充要条件是:每一个点的入度和出度都相等。
欧拉路径的充要条件是:要么是欧拉回路,或者除了起点的出度比入度多一,终点的入度比出度多一,其余的点入度和出度相等。
必要性和上面的证明类似。
3.定理充分性的证明
只要对于每个符合条件的图,只要构造出来即可。
通过 dfs 实现。
直接 dfs,但是有一个问题:如果没有找到所有的边怎么办?
首先,可以明确的是,欧拉路径是又若干个环所组成的。
那么,我们怎样将所有的边接为一个大环呢?
我们模拟栈的回溯过程,将第二个环嵌在第一个环中间。
这个会按照 红-绿 的颜色进行递归的,然后按照这个倒序的话,我们就可以找到这个欧拉回路,也就是这个 红-绿 路径。
栈的模拟十分简单,回溯的时候直接将这条边入栈,最后倒序输出就可以了。
但是,这样的时间复杂度是 $O(nm)$(虽然和 spfa 差不多),我们很多时候需要优化,变为严格 $O(n+m)$。
可以发现,每一次前面遍历过的边,都肯定不会再被用到。
直接将 $head[x]$ 设为当前边的下一条 $next[i]$。
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| #include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std;
const int N=4e5+10,M=4e5+10; int h[N],e[M],ne[M],idx; int din[N],dout[N],ans[M],cnt; int n,m,t; bool vis[N];
void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; }
void dfs(int x) { for (int &i=h[x];~i;) { if (vis[i]) { i=ne[i]; continue; } vis[i]=1; if (t==1) vis[i^1]=1; int now; if (t==1) { now=i/2+1; if (i&1) now=-now; } else now=i+1; int j=e[i]; i=ne[i]; dfs(j); ans[++cnt]=now; } }
int main() { memset(h,-1,sizeof h); cin>>t>>n>>m; int a,b; for (int i=0;i<m;++i) { scanf("%d %d",&a,&b); add(a,b);dout[a]++;din[b]++; if (t==1) add(b,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]) { dfs(i);break; } if (cnt<m) { puts("NO"); return 0; } puts("YES"); for (int i=cnt;i;i--) printf("%d ",ans[i]); return 0;
}
|