UOJ #117. 欧拉回路 (图论基础/欧拉回路)

题目

时间限制:1s 空间限制:256MB

有一天,一位灵魂画师画了一张n个点m条边(1≤n≤1e5,0≤m≤2e5)的图。

现在要你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次。

一共两个子任务:

  1. 这张图是无向图。(50分)
  2. 这张图是有向图。(50分)

图中可能有重边也可能有自环。

如果不可以一笔画,输出一行 “NO”。

否则,输出一行 “YES”,接下来一行输出一组方案。

  1. 如果 t=1,输出 m 个整数 p1,p2,…,pm。令 e=∣pi∣,那么 e 表示经过的第 i 条边的编号。如果 pi为正数表示从 ve 走到 ue,否则表示从 ue 走到 ve。
  2. 如果 t=2,输出 m 个整数 p1,p2,…,pm。其中 pi 表示经过的第 i 条边的编号。

思路来源

「UOJ#117」 欧拉回路 - qwertaya - 博客园

题解

经典Hierholzer算法,复杂度O(E),判断存不存在,先判度,再判图是连通图

有向图欧拉回路:图连通,一个环的情形(所有点入度出度相等),找环上一点输出路径

有向图欧拉路径:图连通,一个环或一条链的情形(所有点入度出度相等,或仅有恰有两个点,其中一个入度=出度+1,另一个出度=入度+1),找环上一点或链的起点输出路径

无向图欧拉回路:图连通,一个环的情形(所有点度都为偶数),找环上一点输出路径

无向图欧拉路径:图连通,一个环或一条链的情形(所有点度都为偶数,或仅有恰有两个度数为奇数的点),找环上一点或链的一端输出路径

欧拉回路性质:可以被拆成若干个环

心得

为什么欧拉回路后序输出?(即如下代码,搜完再输出)

void dfs(int u)
{
        ...
	dfs(v);
	printf("%d %d\n",v,u);
}

考虑1->2->3->2->1的情形,

如若前序,则可能会出现1->2,2->1,2->3,3->2的情况,不连续,故后序

此时a->b->c按v->u输出,会被输出为c->b->a,

如最终需输出a->b->c可将u->v压栈,倒序输出

①注意特判m=0,即没有边的情形

②注意弧优化,如若写成for(i=0;i<e[u].size();i++),会被以下样例卡成O(E*E)

20220213更新:可以用vector的pop_back,把用过的边pop掉,就不用记当前扫到第几条边了

1 200000
1 1
1 1
1 1 
1 1
...

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
#include<set>
using namespace std;
typedef pair<int,int> P;
const int N=1e5+10,M=2e5+10;
vector<P>e[N];
bool vis[M],used[N];
int ans[M],in[N],out[N],cnt;
int op,n,m,u,v;
void dfs(int u)
{
	used[u]=1;
	while(!e[u].empty())
	{   
        P x=e[u].back();e[u].pop_back();
		int v=x.first,id=x.second,fid=abs(id);
		if(vis[fid])continue;
		vis[fid]=1;
		dfs(v);
		ans[++cnt]=id;
	}
}
bool ok()
{
	if(m==0)return 1;//特判没边的情形 
	int pos=1;
	if(op==2)
	{
		for(int i=1;i<=n;++i)
		if(in[i]!=out[i])return 0;
		else if(in[i])pos=i;
	}
	else
	{
		for(int i=1;i<=n;++i)
		if((in[i]+out[i])%2)return 0;
		else if(in[i]+out[i])pos=i;
	}
	dfs(pos);
	for(int i=1;i<=n;++i)
	if((in[i]||out[i])&&!used[i])return 0;
	return 1;
}
int main()
{
	scanf("%d",&op);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&u,&v);
		e[u].push_back(P(v,i));
		if(op==1)e[v].push_back(P(u,-i));
		in[v]++;out[u]++;
	}
	if(!ok())puts("NO");
	else 
	{
		puts("YES");
		for(int i=cnt;i>=1;--i)
		{
			if(op==2)ans[i]=abs(ans[i]);
			printf("%d%c",ans[i],i==1?'\n':' '); 
		}
	}
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值