Farmer John每年有很多柵欄要修理。他總是騎着馬穿過每一個柵欄並修復它破損的地方。
John是一個與其他農民一樣懶的人。他討厭騎馬,因此從來不兩次經過一個柵欄。你必須編一個程序,讀入柵欄網絡的描述,並計算出一條修柵欄的路徑,使每個柵欄都恰好被經過一次。John能從任何一個頂點(即兩個柵欄的交點)開始騎馬,在任意一個頂點結束。
每一個柵欄連接兩個頂點,頂點用1到500標號(雖然有的農場並沒有500個頂點)。一個頂點上可連接任意多(>=1)個柵欄。兩頂點間可能有多個柵欄。所有柵欄都是連通的(也就是你可以從任意一個柵欄到達另外的所有柵欄)。
你的程序必須輸出騎馬的路徑(用路上依次經過的頂點號碼表示)。我們如果把輸出的路徑看成是一個500進制的數,那么當存在多組解的情況下,輸出500進制表示法中最小的一個 (也就是輸出第一位較小的,如果還有多組解,輸出第二位較小的,等等)。
輸入數據保證至少有一個解。
第1行: 一個整數F(1 <= F <= 1024),表示柵欄的數目
第2到F+1行: 每行兩個整數i, j(1 <= i,j <= 500)表示這條柵欄連接i與j號頂點。
輸出應當有F+1行,每行一個整數,依次表示路徑經過的頂點號。注意數據可能有多組解,但是只有上面題目要求的那一組解是認為正確的。
好氣啊,我覺得這個歐拉路求法及其難以接受。。
歐拉路:從一個點出發,遍歷每一個邊剛好一次,到達任意一個點的路徑稱為歐拉路。
若起點和終點不相等,則起點和終點的度數為奇數;
若起點和終點相等,則稱之為歐拉回路,整個無向連通圖的每個點的度數都為偶數。
求法:從一個合法的起點產生一顆搜索樹,對除起點和終點的(在歐拉回路中我們假裝把它們分開考慮)每一個點都可以從一個最早的邊進入,然后從一個最晚的邊出去,我們忽略這兩條邊,對這個點就產生了一個遞歸的子問題,相當於從這個點產生了一個子搜索樹。
當搜索樹產生的點到達搜索樹的起點的時候,我們不對這條路徑進行存儲,因為路徑上的某些點可能產生的搜索樹可能因為遍歷順序的問題還沒有被擴展,而是當回退的時候,我們就用棧存儲這條路徑,這就保證了回退路徑上所有的點都已經對自己的搜索樹進行了擴展。對於每一顆搜索樹的出邊和入邊兩兩進行配對,倒序着形成了一個個環。
倒着存儲的核心,確保路徑上每一點自己的子問題已經解決
為了保證復雜度,我們用前向星遍歷邊之后,得將head數組向后置,以便減少不必要的遍歷。同時,也要判斷每條邊是否被遍歷,因為是雙向邊。
對於此題,如果要保證字典序,則每個點優先遍歷合法的最小的點,這樣小的點就后進棧,當倒序輸出棧的時候,自然也就是最小字典序了。
Code:
#include <cstdio>
#include <iostream>
const int N=502;
const int M=2050;
int g[N][N],head[N],Next[M],to[M],cnt=1,S=N,m,in[N];
void add(int u,int v)
{
Next[++cnt]=head[u];to[cnt]=v;head[u]=cnt;
}
void init()
{
scanf("%d",&m);
int u,v;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
if(u>v) std::swap(u,v);
g[u][v]++;
in[v]++,in[u]++;
S=S<u?S:u;
}
for(int i=500;i;i--)
if(in[i]&1)
S=i;
for(int i=500;i;i--)
for(int j=500;j;j--)
{
while(g[i][j]--)
add(i,j),add(j,i);
}
}
int s[M],tot,vis[M];
void dfs(int now)
{
for(int i=head[now];i;i=Next[i])
{
if(!vis[i])
{
vis[i]=1;
vis[i^1]=1;
head[now]=Next[i];
dfs(to[i]);
}
}
s[++tot]=now;
}
void work()
{
dfs(S);
for(int i=tot;i;i--)
printf("%d\n",s[i]);
}
int main()
{
init();
work();
return 0;
}
2018.7.4
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。