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;
}
简易的代码胜过复杂的说教。
原文地址:https://www.cnblogs.com/bifanwen/p/15755482.html