hdu6311 Cover(歐拉回路)
阿新 • • 發佈:2021-10-05
題意
給定一張無向圖(不保證聯通),求把所有的邊分成若干個集合,使得每個集合都是一個尤拉路徑或歐拉回路,求劃分的集合個數最少,並給出構造方案。
回顧
在圖上用一種走法經過所有的邊恰好一次的路徑叫做尤拉路徑。
如果這條路徑的起點和終點重合,那麼就是歐拉回路。
判斷
-
無向圖
- 歐拉回路:圖中每個點度數為偶。
- 尤拉路徑:恰好有兩個點度數為奇。
-
有向圖
- 歐拉回路:圖中每個點入度 \(=\) 出度
- 尤拉路徑:最多兩個點入度 \(\ne\) 出度,且其中一個點(起點)出度 \(=\) 入度 \(+1\),另一個(終點)入度 \(=\) 出度 \(+1\)。
必要性顯然,下面給出較簡單的構造方法:Hierholzer 演算法。
構造
-
判斷是否存在尤拉路,若只存在路徑則找到起點,若存在迴路則可以以任意點作為起點開始。
-
開始遞迴函式 Hierholzer(u):
- 尋找與 u 相連的邊 (u,v):
- 刪除 (u,v)
- 刪除 (v,u) (無向圖)
- 遞迴 Hierholzer(v)
- 將 u 加入序列尾
- 尋找與 u 相連的邊 (u,v):
-
得到尤拉路序列
此時序列中為找到的尤拉路的點的倒序,求邊的順序稍微修改一下即可。
感性理解一下,Hierholzer 演算法的流程實際上是一開始找到一條簡單路徑(迴路)連線起點到終點作為骨架,然後剩下的一些沒被走到過的邊實際上是一個個小的歐拉回路連線在骨架上,遞迴處理這些小的歐拉回路,最後按順序拼接到骨架上,即可找到整個圖的尤拉路。
思路
首先,如果一張聯通圖沒有奇度點,顯然可以直接分配。
一個經典結論:聯通無向圖中奇度點一定有偶數個。這啟示我們最優分配方案一定是奇度點間兩兩搭配,分別作為一個尤拉路經的起點和終點。
如何構造?
一個很自然的想法就是新建一個超級源點,將所有奇度點向超級源連無向邊,然後從沒有奇度點的聯通塊任意選一個點向超級源連兩條無向邊,這樣就能使整個圖聯通且所有點都是偶度點。
直接對新圖跑尤拉路,得到的邊序列以新加的邊為間隔分成若干段,每段即為一條尤拉路。
程式碼
點選檢視程式碼
#include<bits/stdc++.h> #define RG register #define R RG int #define I inline using namespace std; const int N=1e5+3,M=2e5+3; struct edge{int to,nxt;}e[M<<1]; bool nv[M],dg[N]; int lst[N],tt=1; I void link(int u,int v) { e[++tt]=(edge){v,lst[u]};lst[u]=tt; e[++tt]=(edge){u,lst[v]};lst[v]=tt; dg[u]^=1;dg[v]^=1; } bool vs[N]; int tc; void dfs(int u) { if(vs[u])return; vs[u]=1;++tc; if(dg[u])link(u,0); for(R i=lst[u];i;i=e[i].nxt) dfs(e[i].to); } int sk[M<<1],tk; void find(int ei) { int u=e[ei].to; for(R &i=lst[u];i;i=e[i].nxt) { if(nv[i>>1])continue; nv[i>>1]=1; find(i); } sk[++tk]=ei; } int main() { int n,m,ans=0; scanf("%d%d",&n,&m); for(R i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); link(x,y); } vs[0]=1; for(R i=1;i<=n;i++) { if(vs[i])continue; int nt=tt,nc=tc; dfs(i); if(tc==nc+1)continue; if(tt==nt)link(i,0),link(i,0); ans+=tt-nt>>2; } e[1].to=0; find(1); printf("%d\n",ans); for(R i=tk-1,r=i;i;i--) { int ei=sk[i]; if(ei>>1>m) { if(i<r) { printf("%d",r-i); for(R j=r;j>i;j--) { int ej=sk[j]; printf(" %d",ej&1?-(ej>>1):ej>>1); } puts(""); } r=i-1; } } return 0; }