HiHoCoder 1181 淺談fleury算法求解無向圖歐拉路徑

這裏寫圖片描述
世界真的很大
歐拉路是一個老大難的問題,儘管性質什麼的還是比較熟悉,但忽然想起來好像連求歐拉路的算法都不會哎。。。
然後通過某大佬的blog知道了這道題,算是裸題了吧
題面就非常有意思

description:

在上一回中小Hi和小Ho控制着主角收集了分散在各個木橋上的道具,這些道具其實是一塊一塊骨牌。
這裏寫圖片描述

主角繼續往前走,面前出現了一座石橋,石橋的盡頭有一道火焰牆,似乎無法通過。

小Hi注意到在橋頭有一張小紙片,於是控制主角撿起了這張紙片,只見上面寫着:

將M塊骨牌首尾相連放置於石橋的凹糟中,即可關閉火焰牆。切記骨牌需要數字相同才能連接。
——By 無名的冒險者
小Hi和小Ho打開了主角的道具欄,發現主角恰好擁有M快骨牌。

小Ho:也就是說要把所有骨牌都放在凹槽中才能關閉火焰牆,數字相同是什麼意思?

小Hi:你看,每一塊骨牌兩端各有一個數字,大概是隻有當數字相同時纔可以相連放置,比如:
這裏寫圖片描述

小Ho:原來如此,那麼我們先看看能不能把所有的骨牌連接起來吧

input:

第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

output:

第1行:m+1個數字,表示骨牌首尾相連後的數字

比如骨牌連接的狀態爲(1,5)(5,3)(3,2)(2,4)(4,3),則輸出”1 5 3 2 4 3”

你可以輸出任意一組合法的解。

好久沒見過怎麼良心的題了,還是讓當事人自己來談一下題解吧:

提示:Fleury算法求歐拉路徑

小Ho:這種簡單的謎題就交給我吧!

小Hi:真的沒問題麼?

<10分鐘過去>

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

小Hi:哎,我就知道你會遇到問題。

小Ho:小Hi快來幫幫我!

小Hi:好了,好了。讓我們一起來解決這個問題。

<小Hi思考了一下>

小Hi:原來是這樣。。。小Ho你仔細觀察這個例子:
這裏寫圖片描述

因爲相連的兩個數字總是相同的,不妨我們只寫一次,那麼這個例子可以寫成:3-2-4-3-5-1。6個數字剛好有5個間隙,每個間隙兩邊的數字由恰好對應了一塊骨牌。

如果我們將每一個數字看作一個點,每一塊骨牌看作一條邊。你覺得是怎麼樣的呢?

小Ho:以這個例子來說的話,就是:
這裏寫圖片描述

要把所有的骨牌連起來,也就是把所有的邊都走一次。咦,這不是歐拉路問題麼!

小Hi:沒錯,這問題其實就是一個歐拉路的問題,不過和上一次不一樣的在於,這一次我們要找出一條歐拉路徑。

小Ho:那我們應該如何來找一條路徑呢?

小Hi:我們還是借用一下上次的例子吧
這裏寫圖片描述

使用我們上一次證明歐拉路判定的方法,我們在這個例子中找到了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…一定都滿足起點與終點爲同一個點。所以從任意一個公共節點出發一定有一條路徑回到這個節點。

由此我們得到了一個算法:

在原圖中找一個L1路徑

從L1的終點往回回溯,依次將每個點出棧。並檢查當前點是否還有其他沒有經過的邊。若存在則以當前點爲起點,查找L2,並對L2的節點同樣用棧記錄重複該算法。

當L1中的點全部出棧後,算法結束。

在這裏我們再來一個有3層的例子:
這裏寫圖片描述

在這個例子中:

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來記錄順序,有環直接加就行了
但是有可能第一遍走的時候直接走到了那條路徑而無法回頭
所以我們採用倒着壓棧的方法,可以吧途中遇見的所有點按逆序壓入棧中,這樣就可以實現“插入路徑”的目的

完整代碼:

#include<stdio.h>
#include<stack>
using namespace std;

struct edge
{
    int v,last,mrk;
}ed[2000010];

stack <int> stk;

int n,m,num=1,flag=1;
int head[100010],du[100010];

void add(int u,int v)
{
    num++;
    ed[num].v=v;
    ed[num].mrk=0;
    ed[num].last=head[u];
    head[u]=num;
}

void dfs(int u)
{
    for(int i=head[u];i;i=ed[i].last)
    {
        int v=ed[i].v;
        if(ed[i].mrk) continue ;
        ed[i].mrk=ed[i^1].mrk=1;
        dfs(v);
    }
    stk.push(u);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
        du[u]++,du[v]++;
    }
    for(int i=1;i<=n;i++)
        if(flag && du[i]) dfs(i),flag=0;
    while(!stk.empty())
    {
        printf("%d ",stk.top());
        stk.pop();
    }
    return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/

嗯,就是這樣

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章