歐拉回路模板
題目鏈接:https://www.luogu.com.cn/problem/P6066
非遞歸版代碼 防止棧溢出
歐拉回路就是給一個圖,存在一條迴路把所邊經過且每條邊只經過一次。
對於無向圖:
存在歐拉回路的條件:每個點的度都爲偶數;
存在歐拉路的條件:有且只有兩個點的度爲一,且這兩個點分別爲起點和終點;
對於有向圖:
存在歐拉回路的條件:每個點出度等於入度;
存在歐拉路的條件:存在一個點出度比入度多一作爲起點,存在一點入度比出度多一作爲終點,其餘點出度等於入度;
求歐拉回路的方法——基本(套圓)法
dfs搜索,不能再往下走便回溯,回溯時記錄路徑,回溯時不清除對邊的標記,最後求出來的路徑就是歐拉回路。
舉例
(1)走,然後無路可走,就回溯記錄下回溯路徑,4點有其它路殼走。
(2),無路可走,然後回溯。
記錄下的路徑便是一條歐拉回路。
上述的時間複雜度爲。因爲一個點會被重複遍歷多次。
假設我們採用鄰接表存儲無向圖,我們可以在訪問一條邊後,及時地修改表頭,令它指向下一條邊,這樣我們每次只需取出,就自然跳過了所有已經訪問過的邊。(vis數組突然就沒什麼用了)另外,因爲歐拉回路的DFS的遞歸層數是級別的,很容易造成系統棧的溢出,我們可以用另一個棧,模擬機器的遞歸過程,把代碼轉化爲非遞歸實現,優化後的程序時間複雜度爲
——《算法競賽進階指南》
回到本題,由於題目要求每條邊要正反走兩邊,那麼我們直接把原求歐拉回路模板的vis數組的雙向標記改爲單向標記即可。因爲按照一般的存儲方式,無向邊在鄰接表中是被拆成了兩條有向邊來存儲,若無標記,根據歐拉回路中的更新方式來看,正好會經過兩次。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<queue>
//#define ls (p<<1)
//#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
//#define lowbit(p) p&(-p)
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e4+7;
const int M = 5e5+7;
int head[N],nex[M],ver[M],tot = 1;
bool vis[M];
int stk[M],ans[M];//模擬系統棧,答案棧
int n,m,s,t,cnt,top;
queue<int>q;
void add(int x,int y){
ver[++tot] = y;nex[tot] = head[x];head[x] = tot;
}
void euler(){//模擬dfs
stk[++top] = 1;//起點
while(top > 0){
int x = stk[top],i = head[x];
while(i && vis[i])i = nex[i];//找到一條尚未訪問過的路
// 與x相連的所有邊均已訪問,模擬回溯過程,並記錄
if(i){
stk[++top] = ver[i];
vis[i] = true;//題目要求要回來的嘛,需要標記一次
//vis[i] = ver[i ^ 1] = true;//正常的歐拉回路模板
head[x] = nex[i];
}
else {// 與x相連的所有邊均已訪問,模擬回溯過程,並記錄
top--;
ans[++cnt] = x;
}
}
}
int main(){
scanf("%d%d",&n,&m);
tot = 1;
over(i,1,m){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
euler();
over(i,1,cnt)
printf("%d\n",ans[i]);
}