当前位置: 首页 > news >正文

【欧拉路】学习笔记

定义

欧拉路问题,俗称“一笔画”问题,即给定一张图,若存在一条从点 \(S\) 到点 \(T\) 的路径恰好不重不漏地经过每条边一次,则称该路径为 \(S\)\(T\) 的欧拉路;特别地,若 \(S\)\(T\) 为同一点,则称该路径为欧拉回路

判定

一张无向图有欧拉路,当且仅当所有点度数均为偶数(此时图为欧拉图)或有且仅有两个点度数为奇数(此时图为半欧拉图);有欧拉回路,当且仅当所有点度数均为偶数

一张有向图有欧拉路,当且仅当所有点出度等于入度(此时图为欧拉图)或有且仅有一个点入度恰比出度大 \(1\),一个点入度恰比出度小 \(1\)(此时图为半欧拉图);有欧拉回路,当且仅当所有点入度等于出度

求法

使用 \(DFS\) 遍历即可求出一条欧拉路的可行路径

如果有且仅有两个点度数为奇数,从其中一个点开始遍历;否则因为有回路,任意一点开始遍历即可

考虑经过一个点时,将这个点所在的回路全部加入路径中,最后再加入这个点

直接朴素实现需要一遍 \(DFS\) 找回路并存储,一遍 \(DFS\) 将回路加入最终欧拉路径中,较为麻烦

考虑一个点的回溯代表的含义:要找的欧拉路在这个点之后的点已经走完了

那么这个时候存下这个点,最终即可得到一条 反着的 欧拉路

于是我们在 \(DFS\) 即将回溯的时候直接把点放进一个栈中,最后弹栈就是要找的欧拉路

可能会有一个疑问:为什么不能在 \(DFS\) 的过程中经过一个点就直接把一个点放入路径中,只能在回溯的时候记录?

考虑半欧拉图上搜到某点与那个入度比出度大1/小1的点相连,此时若直接存且不幸先搜到这个奇数点将会直接把这个点放进路径中,相当于提前走上了一条不归路,显然不对;

而在栈的存法下这个奇数点是最先入栈的,即最后输出的,没有问题

模板题代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;inline int read()
{int x=0,fu=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')fu=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*fu;
}
void write(int x)
{if(x<0)	x=-x,putchar('-');if(x<10){putchar(x+'0');return;}write(x/10);putchar(x%10+'0');
}const int N=1e5+10;
vector<pii>ed[N];
int n,m;
int in[N],out[N];
stack<int>st;
int now[N];
void dfs(int u)
{for(int v=now[u];v<ed[u].size();v=now[u]){if(ed[u][v].se)	continue;ed[u][v].se=1;now[u]=v+1;dfs(ed[u][v].fi);}st.push(u);
}signed main()
{n=read(),m=read();for(int i=1;i<=m;i++){int u=read(),v=read();ed[u].push_back({v,0});out[u]++,in[v]++;}int sg1=0,sg2=0;for(int i=1;i<=n;i++){if(in[i]==out[i])	continue;if(in[i]-out[i]==1){if(sg1){puts("No");return 0;}sg1=i;}else if(in[i]-out[i]==-1){if(sg2){puts("No");return 0;}sg2=i;}else{puts("No");return 0;}}if((!sg1&&sg2)||(sg1&&!sg2)){puts("No");return 0;}for(int i=1;i<=n;i++)	sort(ed[i].begin(),ed[i].end());if(sg1)	dfs(min(sg1,sg2));else	dfs(1);while(!st.empty())	write(st.top()),putchar(' '),st.pop();return 0;} 

后记:网络流和欧拉路当前弧优化的区别

在复健图论时无意中发现欧拉路的当前弧优化与 \(dinic\) 不同,然后发现自己其实并没有完全学懂这两个算法
网络流:

for(int i=now[u];~i&&res;i=nxt[i])
{...now[u]=i;...
}

这里的 \(now\) 不能直接变下一条边而只能停在“当前”,因为当前这条边的流量不一定流完,即网络流有可能再用这条边

如果扫到这条边后直接跳了当前弧就会导致增广路的利用其实是不到位的,甚至可能导致一直有增广路但是一直走不到的极端情况

欧拉路:

for(int v=now[u];v<ed[u].size();v=now[u])
{...now[u]=v+1;...
}

这里的 \(now\) 就必须直接变下一条边,因为一条边只需要走一次,下一条边才是“当前”,即 欧拉路不可能再用这条边

如果不跳下一条边这条无用的边就会被再次找,当前弧优化的意义就有限了

http://www.sczhlp.com/news/1577/

相关文章:

  • kafka 日志存储与查询
  • 基于MATLAB的不规则波下结构物波浪力计算
  • Slope Trick
  • ASP.NET WebForms调用ASMX的WebService接口
  • 透视畸变和单应性变换
  • JavaScript中的数据类型以及存储上的差别
  • webstorm关于git很慢的处理
  • 手工测试向左,测试开发向右
  • 设计汽车集群电源 - 详解
  • kafka rocketmq 零拷贝
  • 淀粉质(点分治)总结
  • MATLAB的图像融合方法:IHS、PCA、拉普拉斯、PCNN、小波
  • 基于YOLOv8的有无戴安全帽检测识别项目|完整源码数据集+PyQt5界面+完整训练流程+开箱即用!
  • IDEA Plugins:通义灵码
  • 平衡树
  • Windows 平台的路由表配置
  • 怎么使用德布鲁因序列编码三色激光条纹?
  • BSC链验证者更新机制深度解析:Epoch、Snapshot与实时控制 - 若
  • Iron Software:助力.NET开发者轻松实现文档和图像处理功能
  • 通过对二维地震模拟中有限差分法进行模拟,实现地震合成记录
  • Three.js 的第一个工程-添加文本
  • Random
  • SLF4J Logback Log4j, Log4j2
  • 理解非线性市值因子NLSIZE/MIDCAP
  • 杜教筛
  • Java核心类——3.StringJoiner
  • Messager 详解:WPF 中的消息传递与数据绑定入门指南
  • Fastmcp 案例五(Cherry Studio调式 ,结合案例四)
  • Unity加载资源的方式
  • IMA-Appraisal HASH fix mode和enforce mode的解释