题解 P7771【【模板】欧拉路径】
$\text{upd2021.8.1}:$修改时间复杂度错误的代码,通过评论区的 $\text{hack}$。
$\text{upd2021.8.5}:$增加时间复杂度更优的做法。
题意
给出一个有 $n$ 个点,$m$ 条边构成的有向图,求其字典序最小的欧拉路径。
$1\le n\le10^5,1\le m\le2\times10^5$
思路
是新模板,写个题解。
首先,我们需要了解什么是欧拉路径。
通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉路径,又称欧拉通路、欧拉道路。
如何判断一个图是否存在欧拉路径?
当然,如果图是欧拉图的话,即有欧拉回路时肯定存在欧拉路径。对于其余情况,考虑欧拉路径中相邻两个结点之间有边相连,除了路径中第 $1$ 个和第 $m+1$ 个点,每个点都会被进入一次、离开一次,且每条边都被遍历过。即除了起点、终点,每个点的入度都等于出度。对于起点,入度比出度少一,对于终点,入度比出度大一。
至此,我们就可以 $O(n+m)$ 判断是否有解了。若有解,还可求出起点与终点。
考虑求解,从起点开始 $\text{dfs}$,对于当前搜到的点,随意选择一条以前未选过的与其相连的边继续搜索,退出搜索时压入栈中,最后将栈中所有元素输出即可。
显然,连入每个点和其连出的边都会被遍历一次,对于求任意一条欧拉路径,我们无需考虑每个点被遍历的先后顺序,每次搜至某一点都会使其入度减少一,继续搜时有两种情况:
- 若有回到该点的环,即该点还有入度:
- 回到该点的环,则此次遍历环使得其入度、出度均减一;
- 进入其他环内,则由于连向此点的点还有出度,必有入度,能够对进入的环进行此分析,最终将进入的环内的点的入度均变为 $0$(若里面有终点,其入度为 $1$)后必可回到该点(因为已经证明有欧拉路径);
- 没有回到该点的环,则此时入度已为 $0$,出度只可能为 $1$,向下搜之后此点出、入度均变为 $0$。
最终每个点的入度、出度均变为 $0$,故此算法是正确的,时间复杂度为 $O(n+m)$。
考虑原问题,即求字典序最小的欧拉路径。
考虑贪心,靠前的位置应尽可能小,使每次转移的点尽量小即可,可以预先将每个点连出的所有点按照编号大小排序,时间复杂度 $O(n+m\log m)$。
其中有一个细节:若每扫边次都从 $0$ 开始,即以下搜索代码:
inline void dfs(int x){
for(int i=0;i<a[x].size();i++){
int t=a[x][i].first;
if(!vis[a[x][i].second]){
vis[a[x][i].second]=1;
dfs(t);
}
}
ans.push(x);
}
的复杂度可以通过构造 $10^5$ 条 $1\to2$,$10^5$ 条 $2\to1$ 的边卡到 $O(m^2)$。
解决方案就是记录目前删到第几条边,从删到的下一条边开始枚举,时间复杂度才能正确。
$O(n+m\log m)$ 代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff
}
#define mpr make_pair
const int N=1e5+10;
int cnt,n,m,ind[N],oud[N],st[N];
vector<pair<int,int>>a[N];
bool vis[N<<1];
stack<int>ans;
inline void dfs(int x){
for(int i=st[x];i<a[x].size();i=max(i+1,st[x])){
int t=a[x][i].first;
if(!vis[a[x][i].second]){
vis[a[x][i].second]=1;
st[x]=i+1;
dfs(t);
}
}
ans.push(x);
}
int main(){
int s=0,t=0;
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
oud[u]++,ind[v]++;
a[u].push_back(mpr(v,i));
}
for(int i=1;i<=n;i++)
if(ind[i]!=oud[i]){
cnt++;
if(ind[i]==oud[i]-1)
s=i;
if(ind[i]==oud[i]+1)
t=i;
}
if(cnt!=0&&cnt!=2)
return puts("No"),0;
if(cnt==0)
s=t=1;
if(!s||!t)
return puts("No"),0;
for(int i=1;i<=n;i++)
sort(a[i].begin(),a[i].end());
dfs(s);
while(!ans.empty()){
write(ans.top()),putc(' ');
ans.pop();
}
flush();
}
当然,如果使用计数排序/基数排序等方法可以将时间复杂度降至 $O(n+m)$,但由于常数原因,在实际运行中没有 sort 快,具体代码见这里。
再见 qwq~

浙公网安备 33010602011771号