zoukankan      html  css  js  c++  java
  • 【学习笔记】欧拉路Hierholzer算法-UOJ117 欧拉回路

    (其实我不会念这个算法的名字

    题目链接

    题目解析

    我如果说我现在才会欧拉路还有救吗

    毕竟我关于欧拉路径的题只做过这个-骑马修栅栏,其他时候最多做到过判断是否是欧拉路的题,并没有输出方案过

    考试的时候脑子里完全没有蹦出来这四个字过,然后自己在那儿瞎写(从零开始自己推这个算法,不过我这么菜当然是没有成功,于是成功挂掉)

    咳咳

    网上好多博客都将欧拉路径和欧拉回路混为一谈呐,但实际上是有区别的,欧拉路径是一条经过图中每一条边恰好一次的路径,而欧拉回路要求这条路径是个回路。显然,欧拉回路的要求要高一些,两者的判断条件也有所不同。

    判断条件

    • 欧拉回路

      1. 无向图:所有点的度数都是偶数。
      2. 有向图:所有点出度等于入度。
    • 欧拉路径

      1. 无向图:相比欧拉回路,可以看成有一个起点,一个终点,即可以有两个点的度数是奇数,开始搜索时,起点为其中一个奇点(七桥问题
      2. 有向图:同理,相比欧拉回路,可以看成有一个起点,一个终点,即可以有一个起点是出度=入度+1,一个终点是入度=出度+1
      3. 注意欧拉回路是欧拉路径的一种特殊情况,即要注意考虑没有奇点/所有点出入度都相同的情况。

    算法主体

    从起点开始搜索,走过一条边时,先递归到下一个结点去,然后再把这条边压到栈中。

    因为之前已经判断过这个图是否有欧拉路径,所以可以大胆走,不要害怕出状况,是一定可以绕圈圈绕回来的,而且挨个把邻接边全部搜完,是不会有圈圈搜不到的情况的。

    可以想象一下,类比一下(dfs)树,其实差不多。

    画个图吧:

    一时手边没啥好用的画图软件,将就看吧

    所以一次(dfs)就可以完成了(真的很简单,我为啥之前不会捏

    (所以我看了网上好多博客用了两个栈一出一进的,搞得我很头大

    当前弧优化

    现在的这个算法其实还不够优秀,因为虽然边只会经过一次,但是点却可以经过很多次,而每次回到这个点,你每次都会枚举到所有的边,然后发现它不太对劲(已经枚举过),然后(continue)掉,而这些枚举都是无用的。

    我们要避免这些无用枚举,所以可以像网络流那样加个当前弧优化,就是记录下来你现在枚到哪条边了,下一次就不要枚举那些已经跑过的边了。而我的做法就比较简单粗暴了:因为每条边只跑一次,所以把跑过的边丢掉就好了。

    我其实平时不咋用链式前向星的,一般用(vector),但是我不知道用(vector)怎么写当前弧优化(我尝试过用倒序遍历然后(pop\_back),但是我失败了,而且是(TLE)(代码我放在后面了),估计是因为:它本来是在后面的搜索中才回到当前节点时会遍历掉一些边,那么回溯到之前的一次进入这个节点时,这些边也不需要遍历。而我的写法只能在第二次进入这个节点时,节约掉枚举的那些边,而我的第一次进去的时候就是从所有边开始(for),这个地方并没有优化到。

    (I suppose so,but I possess no evidence.)

    不过用前向星也挺好的(我网络流也是因为这个原因写的前向星

    不过在我写完板子之后被安利了机房大佬的博客,他就是用(vector)写的:Lskr同学的blog

    (不过我懒得改了


    ►Code View

    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    #define N 100005
    #define M 200005
    #define INF 0x3f3f3f3f
    #define LL long long
    int rd()
    {
    	int x=0,f=1;char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	return f*x;
    }
    struct node{
    	int v,nxt,w/*边的编号*/;
    }edge[M<<1];
    int hd[N],cnt=1;//方便找反向边(异或1 
    void add(int u,int v,int w)
    {
    	edge[++cnt].v=v;
    	edge[cnt].w=w;
    	edge[cnt].nxt=hd[u];
    	hd[u]=cnt; 
    }
    
    int T,m,n;
    int fa[N],d[N],ind[N],st[N*10],tot;
    void Init()
    {
    	for(int i=1;i<=n;i++)
    		fa[i]=i;
    }
    int Find(int x)
    {
    	if(fa[x]==x) return x;
    	return fa[x]=Find(fa[x]);
    }
    void Union(int u,int v)
    {
    	u=Find(u),v=Find(v);
    	if(u<v) fa[u]=v;
    	else fa[v]=u;
    }
    void dfs(int u)
    {
    	//本来是:
    	//for(int i=hd[u];i;i=edge[i].nxt)
    	//但这里加了当前弧优化 让hd[]和i一起走 
    	while(hd[u])
    	{
    		int i=hd[u];
    		hd[u]=edge[i].nxt;
    		if(edge[i].w)
    		{//当前边的反向边没有被选 
    			int f=edge[i].w;
    			edge[i].w=0;
    			edge[i^1].w=0;//反向边不能再选 
    			//d[u]--;
    			//d[edge[i].v]--;度数只用于判断是否有解 后面没有实际影响 
    			dfs(edge[i].v);
    			st[++tot]=f; 
    		}
    	}
    	return ;
    }
    void solve1()
    {
    	for(int i=1;i<=m;i++)
    	{
    		int u=rd(),v=rd();
    		add(u,v,i);
    		add(v,u,-i);
    		d[u]++,d[v]++;
    		Union(u,v);
    	}
    	for(int i=1;i<=n;i++)
    		if(d[i]&1)
    		{
    			puts("NO");
    			return ;
    		}
    	int flag=0;
    	for(int i=1;i<=n;i++)
    		if(d[i])
    		{//只要求每条边经过一次 "孤岛"没有影响 
    			if(!flag) flag=Find(i);
    			else if(flag!=Find(i))
    			{
    				puts("NO");
    				return ;
    			}
    		}
    	int s=-1;
    	for(int i=1;i<=n;i++)
    		if(d[i])
    		{
    			s=i;
    			break;
    		}
    	dfs(s);
    	puts("YES"); 
    	while(tot)
    		printf("%d ",st[tot--]);
    }
    void dfs2(int u)
    {
    	while(hd[u])
    	{
    		int i=hd[u];
    		hd[u]=edge[i].nxt;
    		if(edge[i].w)
    		{
    			int f=edge[i].w;
    			edge[i].w=0;
    			dfs2(edge[i].v);
    			st[++tot]=f;
    		}
    	}
    	return ;
    }
    void solve2()
    {
    	for(int i=1;i<=m;i++)
    	{
    		int u=rd(),v=rd();
    		add(u,v,i);
    		d[u]++,ind[v]++;
    		Union(u,v);
    	}
    	for(int i=1;i<=n;i++)
    		if(d[i]!=ind[i])
    		{
    			puts("NO");
    			return ;
    		}
    	int flag=0;
    	for(int i=1;i<=n;i++)
    		if(d[i]||ind[i])
    		{
    			if(!flag) flag=Find(i);
    			else if(flag!=Find(i))
    			{
    				puts("NO");
    				return ;
    			}
    		}
    	int s=-1;
    	for(int i=1;i<=n;i++)
    		if(d[i]||ind[i])
    		{
    			s=i;
    			break;
    		}
    	dfs2(s);
    	puts("YES");
    	while(tot)
    		printf("%d ",st[tot--]);
    }
    int main()
    {
    	T=rd(),n=rd(),m=rd();
    	Init();
    	if(T==1) solve1();
    	else solve2();
    	return 0;
    }
    
    
    

    ►Code View Ver.2

    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    #define N 100005
    #define M 200005
    #define INF 0x3f3f3f3f
    #define LL long long
    int rd()
    {
    	int x=0,f=1;char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	return f*x;
    }
    vector<pair<int,int> >G[N];
    void add(int u,int v,int w)
    {
    	G[u].push_back(make_pair(v,w));
    }
    int Abs(int x)
    {
    	if(x>=0) return x;
    	return -x;
    }
    bool vis[M];
    
    int T,m,n;
    int fa[N],d[N],ind[N],st[N*10],tot;
    void Init()
    {
    	for(int i=1;i<=n;i++)
    		fa[i]=i;
    }
    int Find(int x)
    {
    	if(fa[x]==x) return x;
    	return fa[x]=Find(fa[x]);
    }
    void Union(int u,int v)
    {
    	u=Find(u),v=Find(v);
    	if(u<v) fa[u]=v;
    	else fa[v]=u;
    }
    void dfs(int u)
    {
    	for(int i=G[u].size()-1;i>=0;i--)
    	{
    		int v=G[u][i].first,w=G[u][i].second;
    		if(vis[Abs(w)]) continue;
    		vis[Abs(w)]=1;
    		G[u].pop_back();
    		dfs(v);
    		st[++tot]=w;
    	}
    	return ;
    }
    void solve1()
    {
    	for(int i=1;i<=m;i++)
    	{
    		int u=rd(),v=rd();
    		add(u,v,i);
    		add(v,u,-i);
    		d[u]++,d[v]++;
    		Union(u,v);
    	}
    	for(int i=1;i<=n;i++)
    		if(d[i]&1)
    		{
    			puts("NO");
    			return ;
    		}
    	int flag=0;
    	for(int i=1;i<=n;i++)
    		if(d[i])
    		{//只要求每条边经过一次 "孤岛"没有影响 
    			if(!flag) flag=Find(i);
    			else if(flag!=Find(i))
    			{
    				puts("NO");
    				return ;
    			}
    		}
    	int s=-1;
    	for(int i=1;i<=n;i++)
    		if(d[i])
    		{
    			s=i;
    			break;
    		}
    	dfs(s);
    	puts("YES"); 
    	while(tot)
    		printf("%d ",st[tot--]);
    }
    void dfs2(int u)
    {
    	for(int i=G[u].size()-1;i>=0;i--)
    	{
    		int v=G[u][i].first,w=G[u][i].second;
    		if(vis[w]) continue;
    		vis[w]=1;
    		G[u].pop_back();
    		dfs(v);
    		st[++tot]=w;
    	}
    	return ;
    }
    void solve2()
    {
    	for(int i=1;i<=m;i++)
    	{
    		int u=rd(),v=rd();
    		add(u,v,i);
    		d[u]++,ind[v]++;
    		Union(u,v);
    	}
    	for(int i=1;i<=n;i++)
    		if(d[i]!=ind[i])
    		{
    			puts("NO");
    			return ;
    		}
    	int flag=0;
    	for(int i=1;i<=n;i++)
    		if(d[i]||ind[i])
    		{
    			if(!flag) flag=Find(i);
    			else if(flag!=Find(i))
    			{
    				puts("NO");
    				return ;
    			}
    		}
    	int s=-1;
    	for(int i=1;i<=n;i++)
    		if(d[i]||ind[i])
    		{
    			s=i;
    			break;
    		}
    	dfs2(s);
    	puts("YES");
    	while(tot)
    		printf("%d ",st[tot--]);
    }
    int main()
    {
    	T=rd(),n=rd(),m=rd();
    	Init();
    	if(T==1) solve1();
    	else solve2();
    	return 0;
    }
    
    
  • 相关阅读:
    Openlayers 3 热力图
    javaScript 新学习:Array.contains 函数
    将页面内容转为Excel下载
    Cookie 的设置和获取
    escape()、encodeURI()、encodeURIComponent()区别详解
    java 对象与二进制互转
    获取与当前类同级目录下的文件
    Windows下比较小巧的c/c++ ide
    保存到properties
    javafx 普通弹框提示
  • 原文地址:https://www.cnblogs.com/lyttt/p/14030624.html
Copyright © 2011-2022 走看看