hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)


题目链接


#1181 : 欧拉路·二

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

在上一回中小Hi和小Ho控制着主角收集了分散在各个木桥上的道具,这些道具其实是一块一块骨牌。

hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)_第1张图片

主角继续往前走,面前出现了一座石桥,石桥的尽头有一道火焰墙,似乎无法通过。

小Hi注意到在桥头有一张小纸片,于是控制主角捡起了这张纸片,只见上面写着:

将M块骨牌首尾相连放置于石桥的凹糟中,即可关闭火焰墙。切记骨牌需要数字相同才能连接。
——By 无名的冒险者

小Hi和小Ho打开了主角的道具栏,发现主角恰好拥有M快骨牌。

小Ho:也就是说要把所有骨牌都放在凹槽中才能关闭火焰墙,数字相同是什么意思?

小Hi:你看,每一块骨牌两端各有一个数字,大概是只有当数字相同时才可以相连放置,比如:

小Ho:原来如此,那么我们先看看能不能把所有的骨牌连接起来吧。

 

提示:Fleury算法求欧拉路径

 

输入

第1行:2个正整数,N,M。分别表示骨牌上出现的最大数字和骨牌数量。1≤N≤1,000,1≤M≤5,000

第2..M+1行:每行2个整数,u,v。第i+1行表示第i块骨牌两端的数字(u,v),1≤u,v≤N

输出

第1行:m+1个数字,表示骨牌首尾相连后的数字

比如骨牌连接的状态为(1,5)(5,3)(3,2)(2,4)(4,3),则输出"1 5 3 2 4 3"

你可以输出任意一组合法的解。

样例输入
5 5
3 5
3 2
4 2
3 4
5 1
样例输出
1 5 3 4 2 3





hihocoder上对欧拉路判定定理的解释:

小Ho:好麻烦啊,是我的话就随便走几步,到没路可走不就好了么!

小Hi:那样的话,收集的道具会少很多,万一以后要用到,又得重新读档了。

小Ho:好吧,让我先想想。

<两分钟后>

小Ho:这个好像是一笔画问题哎,我们是在求一个方法能够一笔画出所有边吧?

小Hi:没错,这就是一笔画问题,不过它更正式的名字叫做欧拉路问题。其定义是

给定无孤立结点图G,若存在一条路,经过图中每边一次且仅一次,该条路称为欧拉路。

小Ho:既然有名字,那就证明这东西有解咯?

小Hi:没错,欧拉路是有判定条件的:一个无向图存在欧拉路当且仅当该图是连通的且有且只有2个点的度数是奇数,此时这两个点只能作为欧拉路径的起点和终点。

若图中没有奇数度的点,那么起点和终点一定是同一个点,这样的欧拉路叫做欧拉回路

对于任意一个点来说,从其他点到它的次数和从它到其他点的次数必然是相等的,否则就会出现出去次数和进入次数不同。若进入次数多,则该点位终点,若出去次数多则该点为起点。

对于一个无向图来说,进入和出去的次数恰好反映在度的数量上。所以奇数度的点至多只能有2个。

严格的证明的话:

若图G连通,有零个或两个奇数度结点,我们总有如下方法构造一条欧拉路:

  1. 若有两个奇数度结点,则从其中的一个结点开始构造一条迹,即从v[0]出发经关联边e[1]“进入”v[1],若v[1]的度数为偶数,则必可由v[1]再经关联边e[2]进入v[2],如此进行下去,每边仅取一次。由于G是连通的,故必可到达另一奇数度结点停下,得到一条迹L:v[0]-e[1]-v[1]-e[2]…v[i]-e[i+1]…v[k]。若G中没有奇数度结点则从任一结点v[0]出发,用上述方法必可回到结点v[0],得到上述一条闭迹L1。
  2. 若L1通过了G的所有边,则L1就是欧拉路。
  3. 若G中去掉L1后得到子图G′,则G′中每个结点度数为偶数,因为原来的图是连通的,故L1与G′至少有一个结点v[i]重合,在G′中由v[i]出发重复第一步的方法,得到闭迹L2。
  4. 当L1与L2组合在一起,如果恰是G,则即得欧拉路,否则重复第三步可得到闭迹L3,以此类推直到得到一条经过图G中所有边的欧拉路。

不妨看看前面的例子:

对于这个图来说,编号为4,5的点度数为奇数,其他为偶数。根据上面的性质,我们知道起点和终点一定是4、5节点。我们先从4开始随便画一条边直到无路可走:

hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)_第2张图片

在这一步中我们连接了4-5-6-3-2-5。根据欧拉路的构造,我们得到了L1。因为L1并没有走过所有的边,所以我们执行步骤3,可以发现对于4和2都是与子图G'重合的点,在子图上我们可以得到L2(2-4-1-2):

hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)_第3张图片

L1和L2合并就构成了欧拉路。

小Ho:既然有这个性质,那么我只需要计算每个点的度数就能知道能否走过所有的边了。

小Hi:没错,但是别忘了最重要的一点,需要整个图是连通的才行。








hihocoder上对于此题的解释:

小Ho:这种简单的谜题就交给我吧!

小Hi:真的没问题么?

<10分钟过去>

小Ho:啊啊啊啊啊!搞不定啊!!!骨牌数量一多就乱了。

小Hi:哎,我就知道你会遇到问题。

小Ho:小Hi快来帮帮我!

小Hi:好了,好了。让我们一起来解决这个问题。

<小Hi思考了一下>

小Hi:原来是这样。。。小Ho你仔细观察这个例子:

因为相连的两个数字总是相同的,不妨我们只写一次,那么这个例子可以写成:3-2-4-3-5-1。6个数字刚好有5个间隙,每个间隙两边的数字由恰好对应了一块骨牌。

如果我们将每一个数字看作一个点,每一块骨牌看作一条边。你觉得是怎么样的呢?

小Ho:以这个例子来说的话,就是:

hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)_第4张图片

要把所有的骨牌连起来,也就是把所有的边都走一次。咦,这不是欧拉路问题么!

小Hi:没错,这问题其实就是一个欧拉路的问题,不过和上一次不一样的在于,这一次我们要找出一条欧拉路径。

小Ho:那我们应该如何来找一条路径呢?

小Hi:我们还是借用一下上次的例子吧

hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)_第5张图片

使用我们上一次证明欧拉路判定的方法,我们在这个例子中找到了2条路径:

L1: 4-5-2-3-6-5
L2: 2-4-1-2

假设我们栈S,记录我们每一次查找路径时的结点顺序。当我们找到L1时,栈S内的情况为:

S: 4 5 2 3 6 5 [Top]

此时我们一步一步出栈并将这些边删除。当我们到节点2时,我们发现节点2刚好是L1与L2的公共节点。并且L2满足走过其他边之后回到了节点2。如果我们在这个地方将L2先走一遍,再继续走L1不就刚好走过了所有边么。

而且在上一次的证明中我们知道,除了L1之外,其他的路径L2、L3...一定都满足起点与终点为同一个点。所以从任意一个公共节点出发一定有一条路径回到这个节点。

由此我们得到了一个算法:

  1. 在原图中找一个L1路径

  2. 从L1的终点往回回溯,依次将每个点出栈。并检查当前点是否还有其他没有经过的边。若存在则以当前点为起点,查找L2,并对L2的节点同样用栈记录重复该算法。

  3. 当L1中的点全部出栈后,算法结束。

在这里我们再来一个有3层的例子:

hihocoder 1181欧拉路·二(求欧拉路Fleury(佛罗莱)算法)_第6张图片

在这个例子中:

L1: 1-2-6-5-1
L2: 2-3-7-2
L3: 3-4-8-3

第一步时我们将L1压入栈S,同时我们用一个数组Path来记录我们出栈的顺序:

S: [1 2 6 5 1]
Path:

然后出栈到节点2时我们发现了2有其他路径,于是我们把2的另一条路径加入:

S: 1 [2 3 7 2]
Path: 1 5 6

此时L2已经走完,然后再开始弹出元素,直到我们发现3有其他路径,同样压入栈:

S: 1 2 [3 4 8 3]
Path: 1 5 6 2 7 

之后依次弹出剩下的元素:

S: 
Path: 1 5 6 2 7 3 8 4 3 2 1

此时的Path就正好是我们需要的欧拉路径。

小Ho:原来这样就能求出欧拉路,真是挺巧妙的。

小Hi:而且这个算法在实现时也有很巧妙的方法。因为DFS本身就是一个入栈出栈的过程,所以我们直接利用DFS的性质来实现栈,其伪代码如下:

DFS(u):
	While (u存在未被删除的边e(u,v))
		删除边e(u,v)
		DFS(v)
	End
	PathSize ← PathSize + 1
	Path[ PathSize ] ← u

小Ho:这代码好简单,我觉得我可以实现它!

小Hi:那么实现就交给你了

小Ho:没问题!交给我吧







那么,我们可以通过这个方式找到欧拉路:

DFS(u):
	While (u存在未被删除的边e(u,v))
		删除边e(u,v)
		DFS(v)
	End
	PathSize ← PathSize + 1
	Path[ PathSize ] ← u
Path中存的就是路径了。如果要存路径(边)在删除边e(u,v)时加入答案进行储存就行了。








不过注意这一题有个track,就是可以有重边,存图的时候要注意,dfs时删边也要注意一下

#include
#include
#include
#include
#include
#include
using namespace std;
const int MAXN=1000+100;
set g[MAXN];
int gg[MAXN][MAXN];
int path[5000*4];
int deg[MAXN];
int sz;
void dfs(int u)
{
	while(g[u].size())
	{
		int v=*g[u].begin();
		gg[u][v]--,gg[v][u]--;
		if(!gg[u][v])
		g[u].erase(v);
		if(!gg[v][u])
		g[v].erase(u);
		dfs(v);
	}
	sz++;
	path[sz]=u;
}
void init()
{
	sz=0;
	for(int i=0;i


另外一个Fleury(佛罗莱)算法模版:

#include
#include
const int MAXN=111;
using namespace std;

stackS;
int edge[MAXN][MAXN];
int n,m;

void dfs(int x){
    S.push(x);
    for(int i=1;i<=n;i++){
        if(edge[x][i]>0){
            edge[i][x]=edge[x][i]=0;//删除此边
            dfs(i);
            break;
        }
    }
}

//Fleury算法的实现
void Fleury(int x){
    S.push(x);
    while(!S.empty()){
        int b=0;
        for(int i=1;i<=n;i++){
            if(edge[S.top()][i]>0){
                b=1;
                break;
            }
        }
        if(b==0){
            printf("%d",S.top());
            S.pop();
        }else {
            int y=S.top();
            S.pop();
            dfs(y);//如果有,就dfs
        }
    }
    printf("\n");
}

int main(){
    scanf("%d%d",&n,&m); //读入顶点数以及边数
    memset(edge,0,sizeof(edge));
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        edge[x][y]=edge[y][x]=1;
    }
    //如果存在奇数顶点,则从奇数顶点出发,否则从顶点0出发
    int num=0,start=1;
    for(int i=1;i<=n;i++){                        //判断是否存在欧拉回路
        int degree=0;
        for(int j=1;j<=n;j++){
            degree+=edge[i][j];
        }
        if(degree&1){
            start=i,num++;
        }
    }
    if(num==0||num==2){
        Fleury(start);
    }else
        printf("No Euler Path\n");
    return 0;
}

你可能感兴趣的:(欧拉回路,图论,模版)