【歐拉回路】解題報告:luogu P6066 [USACO]Watchcow (歐拉回路詳解)【模板】

歐拉回路模板

題目鏈接:https://www.luogu.com.cn/problem/P6066
在這裏插入圖片描述

O(N+M)O(N+M)非遞歸版代碼 防止棧溢出

歐拉回路就是給一個圖,存在一條迴路把所邊經過且每條邊只經過一次。

對於無向圖:

存在歐拉回路的條件:每個點的度都爲偶數;
  
存在歐拉路的條件:有且只有兩個點的度爲一,且這兩個點分別爲起點和終點;

對於有向圖:

存在歐拉回路的條件:每個點出度等於入度;

存在歐拉路的條件:存在一個點出度比入度多一作爲起點,存在一點入度比出度多一作爲終點,其餘點出度等於入度;

求歐拉回路的方法——基本(套圓)法
  dfs搜索,不能再往下走便回溯,回溯時記錄路徑,回溯時不清除對邊的標記,最後求出來的路徑就是歐拉回路。

舉例

在這裏插入圖片描述

(1)走<1,2>,<2,3>,<3,4>,<4,5>,<5,1><1,2>,<2,3>, < 3,4>,<4,5>,<5,1>,然後無路可走,就回溯記錄下回溯路徑<1,5>,<5,4><1,5>,<5,4>,4點有其它路殼走。
(2)<4,8>,<8,3>,<3,6>,<6,7>,<7,2><2,4><4,8>,<8,3>,<3,6>,<6,7>,<7,2>,<2,4>,無路可走,然後回溯<1,5>,<5,4>,<4,2>,<2,7>,<7,6>,<6,3>,<3,8>,<8,4>,<4,3>,<3,2>,<2,1><1,5>,<5,4>,<4,2>,<2,7>,<7,6>,<6,3>,<3,8>,<8,4>,<4,3>,<3,2>,<2,1>
記錄下的路徑<1,5>,<5,4>,<4,2>,<2,7>,<7,6>,<6,3>,<3,8>,<8,4>,<4,3>,<3,2>,<2,1><1,5>,<5,4>,<4,2>,<2,7>,<7,6>,<6,3>,<3,8>,<8,4>,<4,3>,<3,2>,<2,1>便是一條歐拉回路。

上述的時間複雜度爲O(NM)O(NM)。因爲一個點會被重複遍歷多次。
假設我們採用鄰接表存儲無向圖,我們可以在訪問一條邊(x,y)(x,y)後,及時地修改表頭head[x]head[x],令它指向下一條邊,這樣我們每次只需取出head[x]head[x],就自然跳過了所有已經訪問過的邊。(vis數組突然就沒什麼用了)

另外,因爲歐拉回路的DFS的遞歸層數是O(M)O(M)級別的,很容易造成系統棧的溢出,我們可以用另一個棧,模擬機器的遞歸過程,把代碼轉化爲非遞歸實現,優化後的程序時間複雜度爲O(N+M)O(N+M)


——《算法競賽進階指南》

回到本題,由於題目要求每條邊要正反走兩邊,那麼我們直接把原求歐拉回路模板的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]);
}

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