D164【模板】无向图 欧拉路径 欧拉回路 P2731 [USACO3.3] 骑马修栅栏
D164【模板】无向图 欧拉路径 欧拉回路 P2731 [USACO3.3] 骑马修栅栏_哔哩哔哩_bilibili
每条边恰好经过一次的路径称为欧拉路径,每条边恰好经过一次又回到起点的路径称为欧拉回路,通称一笔画问题

无向图的欧拉回路的判定:所有点的度都是偶数(即 0 个奇点),如图 1
无向图的欧拉路径的判定:有且仅有两个点的度是奇数(即 2 个奇点),这两个点分别作起点与终点,如图 2
图 3 存在 4 个奇点,不满足条件,不存在欧拉路径
P2731 [USACO3.3] 骑马修栅栏 Riding the Fences - 洛谷
无向连通图,n($\le500$) 个点,m($\le1024$) 条边,输出字典序最小的欧拉路径
思路
点数很少,考虑用邻接矩阵 $g[i][j]$ 存图,从起点 $s$ 开始遍历,从小到大枚举邻接点 $j$,就能满足字典序最小
题目保证有解,首先找出度数为奇数的起点 $s$,然后深搜遍历,每经过一条边就先删除它,避免走两次
路径不能先序输出,要后序时刻入栈,最后从栈中弹出,为什么?

如果先序输出,结果为 1,2,5,3,4,1 不对。后序入栈,序列为 5,2,1,4,3,1,弹出结果 1,3,4,1,2,5, 正确
因为先序输出通往奇数点的路径,后面路径无法到达
当一个点的全部出边都经过之后,表示这个点不能再到达其他点,为了到达其他点,只能让这个点先入栈,在其他点之后出栈
时间复杂度:需要遍历 n 个点,每个点需要枚举 n 次邻接点,所以 $O(n^2)$
// 欧拉路径 欧拉回路 O(n^2) #include<bits/stdc++.h> using namespace std; const int n=500,N=505; int m,du[N]; //度 stack<int> path; int g[N][N]; //邻接矩阵 void dfs(int x){ for(int i=1; i<=n; i++){ if(g[x][i]){ //如果边(x,i)存在 g[x][i]--, g[i][x]--; //删除边 dfs(i); //走到i } } path.push(x); //后序记录路径 } int main(){ cin>>m; for(int a,b;m--;){ cin>>a>>b; g[a][b]++, g[b][a]++; //累计两点之间的边 du[a]++, du[b]++; //累计端点的度 } int start=1; while(!du[start]) start++; //排除度数为0的点 for(int i=1; i<=n; i++)if(du[i]&1){ //查找度数为奇数的点 start=i; break; } dfs(start); while(!path.empty())printf("%d\n",path.top()),path.pop(); }
无向边即双向边,用 vector 存邻结点,对每个点的邻接点排序后,如图
关键是预处理每条边的反边的终点的下标位置,以便搜索时,对正反边同时打上删除标记
预处理:$e[1][4].idx=pos[e[1][4].to]++;\;→ e[1][4].idx=0$;即边 (1,4) 的反边 (4,1) 的终点 1 的下标为 0
$e[3][4].idx=pos[e[3][4].to]++;\;→ e[3][4].idx=1$;即边 (3,4) 的反边 (4,3) 的终点 3 的下标为 1
打标记:$e[1][4].del=true;\;e[e[1][4].to][e[1][4].idx].del=e[4][\color{red}{0}].del=true$;
$e[3][4].del=true;\;e[e[3][4].to][e[3][4].idx].del=e[4][\color{red}{1}].del=true$;

这样,排序决定时间复杂度 $O(mlogm)$
// 欧拉路径 欧拉回路 O(mlogm) #include<bits/stdc++.h> using namespace std; const int n=500,N=505; int m,du[N]; stack<int> path; struct E{ int to; //终点 int idx; //反边的终点的下标 bool del; //删除标记 }; vector<E> e[N]; //邻接表 int pos[N]; //pos[x]记录点x的下标编号 int p[N]; //p[x]表示点x当前处理到第几条出边,初始值为0,相当于全局指针 void dfs(int x){ for(int i=p[x]; i<e[x].size(); i=p[x]){ p[x]=i+1; //全局指针,指向下一条边 E &a=e[x][i]; if(!a.del){ a.del=e[a.to][a.idx].del=true; //打删除标记 dfs(a.to); } } path.push(x); //后序记录路径 } int main(){ scanf("%d",&m); for(int i=1,a,b; i<=m; ++i){ scanf("%d %d",&a,&b); e[a].push_back({b,0,0}); e[b].push_back({a,0,0}); ++du[a]; ++du[b]; } for(int i=1; i<=n; ++i)if(!e[i].empty()) sort(e[i].begin(),e[i].end(),[&](E a,E b){return a.to<b.to;}); //点i的邻接点升序 for(int i=1; i<=n; ++i)for(int j=0; j<e[i].size(); ++j) e[i][j].idx=pos[e[i][j].to]++; //记录e[i][j]的反边的终点的下标 int start=1; while(!du[start]) start++; //排除度数为0的点 for(int i=1; i<=n; i++)if(du[i]&1){ //查找度数为奇数的点 start=i; break; } dfs(start); while(!path.empty())printf("%d\n",path.top()),path.pop(); }
浙公网安备 33010602011771号