AcWing 算法提高课 欧拉回路和欧拉路径
定义:经过每一条边且每一条边恰好只经过一次
一、无向图中,当所有边都连通时:
存在欧拉路径,等价于,图中度为奇数的点只有0或2个。
存在欧拉回路,等价于,图中度为奇数的点只有0个。
二、有向图中,当所有边都连通时:
存在欧拉路径,充要条件,要么所有点的出度等于入度,要么除了两个点之外,其余所有点的出度等于入度,且剩余两个点,一个满足出度比入度多1(起点),另一个入度比出度多1(终点)。
存在欧拉回路,充要条件,所有点的出度等于入度。
模板:
欧拉回路模板:(记录边)

const int N=100010; const int M=400010; vector<int> adj[N]; vector<int> id[N]; int idx; int n,m; bool used[M]; int ans[M],cnt; int din[N],dout[N]; void DFS1(int u) { //注意由于可能存在环,adj[u]可能被改变 //故不能使用for(int i=adj[u].size()-1;i>=0;i--)这样的循环 while(adj[u].size()) { int eid=id[u].back(); int nxt=adj[u].back(); if(used[eid])//已经被其反向边遍历过 { adj[u].pop_back(); id[u].pop_back(); continue; } adj[u].pop_back(); id[u].pop_back(); used[eid^1]=true;//无向图标记反向边 DFS1(nxt); int t=eid/2+1;//由于无向图建了两条边,故需要获取边的真实编号 if(eid&1) ans[++cnt]=-t;//反向边 else ans[++cnt]=t;//正向边 } } void DFS2(int u) { //注意由于可能存在环,adj[u]可能被改变 //故不能使用for(int i=adj[u].size()-1;i>=0;i--)这样的循环 while(adj[u].size()) { int eid=id[u].back(); int nxt=adj[u].back(); adj[u].pop_back(); id[u].pop_back(); DFS2(nxt); ans[++cnt]=eid+1; } } void YD() { int t;cin>>t; cin>>n>>m; fore(i,1,n) { adj[i].clear(); id[i].clear(); } memset(din,0,sizeof(din)); memset(dout,0,sizeof(dout)); if(t==1)//无向图 { idx=0; fore(i,1,m) { int a,b;cin>>a>>b; adj[a].pub(b); id[a].pub(idx++); adj[b].pub(a); id[b].pub(idx++); din[b]++; dout[a]++; } fore(i,1,n) { if((din[i]+dout[i])%2) { cout<<"NO"<<endl; return; } } fore(i,1,n) { if(adj[i].size())//找到有边相连的一个点作为起点 { DFS1(i); break; } } if(cnt<m)//图不是连通的 { cout<<"NO"<<endl; return; } cout<<"YES"<<endl; for(int i=cnt;i>0;i--)//由于深搜,所以最后一个点是起点 { cout<<ans[i]<<' '; } cout<<endl; } else if(t==2)//有向图 { fore(i,1,m) { int a,b;cin>>a>>b; adj[a].pub(b); id[a].pub(idx++); din[b]++; dout[a]++; } fore(i,1,n) { if(din[i]!=dout[i]) { cout<<"NO"<<endl; return; } } fore(i,1,n) { if(adj[i].size())//找到有边相连的一个点作为起点 { DFS2(i); break; } } if(cnt<m)//图不是连通的 { cout<<"NO"<<endl; return; } cout<<"YES"<<endl; for(int i=cnt;i>0;i--)//由于深搜,所以最后一个点是起点 { cout<<ans[i]<<' '; } cout<<endl; } }
t==1为无向图,t==2为有向图
例题:https://www.acwing.com/problem/content/1186/
注意:
1、欧拉回路问题在dfs中,需要用边来判重,如果每次都遍历所有边,最差需要O(m^2),所以需要优化,比如每次遍历到一个边就把它删掉。
2、对于无向图,由于会建立两条边,故遍历时也需要删去其反向边。由于正反边的输入次序(标号)仅差1,可以采用标记法处理:建立边标记数组,在邻接表中插入边时,也记录边的标号,然后再删除标号为idx的边时,也将标号为idx^1的边进行标记。
欧拉路径模板:DFS和欧拉回路模板一样,判断是否存在以及起点的方式不同
无向图欧拉路径模板:(记录点,且按模板排序后为最小字典序)

const int N=510; const int M=1100; int ans[M]; vector<PII> adj[N]; vector<int> id[N]; int idx=0; int cnt=0; bool used[2*M]; int din[N]; int dout[N]; void DFS(int u) { while(adj[u].size()) { auto [nxt,eid]=adj[u].back(); if(used[eid]) { adj[u].pop_back(); continue; } adj[u].pop_back(); used[eid^1]=true; DFS(nxt); } ans[++cnt]=u; } void YD() { int m;cin>>m; fore(i,1,m) { int a,b;cin>>a>>b; adj[a].push_back({b,idx++}); adj[b].push_back({a,idx++}); din[a]++; dout[b]++; } fore(i,1,N) { sort(adj[i].begin(),adj[i].end(),greater<PII>()); } bool flag=false; fore(i,1,N) { if((din[i]+dout[i])%2)//是否有固定的起点 { flag=true; DFS(i); break; } } if(!flag) { DFS(1); } rofe(i,cnt,1) { cout<<ans[i]<<endl; } }
例题:https://www.acwing.com/problem/content/1126/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人