題目名稱:歐拉路·二
題目鏈接:http://hihocoder.com/contest/hiho50/problem/1
描述
在上一回中小Hi和小Ho控制着主角收集了分散在各個木橋上的道具,這些道具其實是一塊一塊骨牌。
主角繼續往前走,面前出現了一座石橋,石橋的盡頭有一道火焰牆,似乎無法通過。
小Hi注意到在橋頭有一張小紙片,於是控制主角撿起了這張紙片,只見上面寫着:
將M塊骨牌首尾相連放置於石橋的凹糟中,即可關閉火焰牆。切記骨牌需要數字相同才能連接。 ——By 無名的冒險者
小Hi和小Ho打開了主角的道具欄,發現主角恰好擁有M快骨牌。
小Ho:也就是說要把所有骨牌都放在凹槽中才能關閉火焰牆,數字相同是什麼意思?
小Hi:你看,每一塊骨牌兩端各有一個數字,大概是隻有當數字相同時纔可以相連放置,比如:
小Ho:原來如此,那麼我們先看看能不能把所有的骨牌連接起來吧。
輸入
第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
提示: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:沒問題!交給我吧
這道題開始一直在糾結到底要不要判斷是否爲歐拉道路,然後發現並不用。。。
代碼如下:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<stack>
using namespace std;
int n,m,cnt=0;
int G[1003][1003];
int path[10003],degree[1003];
void dfs(int u)
{
for(int v=1;v<=n;v++)
if(v!=u&&G[u][v])
{
G[u][v]--;
G[v][u]--;
dfs(v);
}
path[++cnt]=u;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(G,0,sizeof(G));
memset(degree,0,sizeof(degree));
int u,v;
cnt=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
G[u][v]++; //因爲可能有重邊,所以得直接統計邊的數量
G[v][u]++;
degree[u]++;
degree[v]++;
}
int z=1;
for(int i=n;i>=1;i--)
{
if(degree[i]&1) //如果存在奇數頂點,則從奇數頂點出發,否則從頂點1出發
{
z=i;
break;
}
}
dfs(z);
for(int j=1;j<cnt;j++)
printf("%d ",path[j]);
printf("%d\n",path[cnt]);
}
return 0;
}