P7771 【模板】欧拉路径
简要题意:
给定 \(n\) 个点 \(m\) 条边的有向图,输出其字典序最小的欧拉路径。无解则输出 No
.
\(n \leq 10^5 , m \leq 2 * 10^5\).
注:图中可能存在重边,自环。
欧拉路径,即无重复无遗漏地经过所有边的一条路径。
根据欧拉路径的定义,容易得到,如果一个有向图存在欧拉路径,则必须有:
- 要么每个点的入度都等于出度,此时起点终点任意;要么除了两个点外的点入度等于出度,这两个点必有一个出度比入度大一,为唯一起点;另一个入度比出度大一,为唯一终点。
于是我们可以成功判断无解情况,并对合法的情况,构造出起点和终点。
构造路径的方法不难。每次都走当前点未走过的一条字典序最小的边,这样答案一定是最优的。因为字典序的特点是,一步优则全局优。
注意到一个问题。我们可以对边进行排序(利用 vector
即可),然后按照顺序去走。同时用桶维护每个边是否被走过。
然而此时,其实,时间复杂度是 \(\mathcal{O}(n + m^2)\).
因为每次到一个点,都会把先前已经走过的边再次走一遍。这显然,会被卡掉。比如一个很简单的例子,\(n = 10^5 , m = 2 * 10^5\),其中有一半边是 \(1 \rightarrow 2\),另一半是 \(2 \rightarrow 1\),此时你就会因为扫边到 \(m^2\) 级别而导致超时。
而问题的解决也比较容易想。每次开一个数组 st
,记录我们应该从第几条边开始,每走一条就更新一下,这样每条边就真正只会被扫描一次,而不是仅仅走一次、扫描多次。
时间复杂度:\(\mathcal{O}(n + m \log m)\).
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+1;
int n,m,S,T,cnt=0;
int in[N],out[N],st[N];
vector<pair<int,int> > G[N];
stack<int> s;
bool h[N];
inline void dfs(int x) {
for(int i=st[x];i<G[x].size();i=max(i+1,st[x])) {
int y = G[x][i].first , z = G[x][i].second;
if(h[z]) continue; h[z] = 1; st[x] = i + 1;
dfs(y);
} s.push(x);
}
int main() {
scanf("%d %d",&n,&m);
while(m--) {
int u,v;
scanf("%d %d",&u,&v);
out[u]++; in[v]++;
G[u].push_back(make_pair(v,++cnt));
}
int flag = 0;
for(int i=1;i<=n;i++) {
int p = in[i] - out[i];
if(p) flag++;
if(p == 1) S = i;
if(p == -1) T = i;
}
if(flag && flag!=2) return puts("No"),0;
if(flag == 0) S = T = 1;
if(!S || !T) return puts("No"),0;
for(int i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
dfs(T);
while(!s.empty()) {
printf("%d ",s.top());
s.pop();
}
return 0;
}
简易的代码胜过复杂的说教。