圖論歐拉回路初步 & BZOJ2095 POI2010 Bridges



反正對於現在的我來說是好題。順便膜po大犇dingchao大犇

網絡流什么的還是再開一個專題好了。

歐拉回路問題參考論文《歐拉回路性質與應用探究》by 仇榮琦。


POI2010 題解整理

Description

小C為了減肥,他來到了瘦海,這是一個巨大的海,海中有n個小島,小島之間有m座橋連接,兩個小島之間不會有兩座橋,並且從一個小島可以到另外任意一個小島。

現在小C想騎單車從小島1出發,騎過每一座橋,到達每一個小島,然后回到小島1。小Q為了讓小C減肥成功,召喚了大風,由於是海上,風變得十分大,經過每一座橋都有不可避免的風阻礙小C。小C十分ddt,於是用泡芙賄賂了你,希望你能幫他找出一條承受的最大風力最小的路線。

注意的是每條邊能且只能經過一次,即歐拉回路

Input

  • Line 1:兩個為空格隔開的整數 n m
  • Line 2~m+1:每行由空格隔開的4個整數 a b c d 組成,表示第 i+1 行第 i 座橋連接小島 a b ,從 a b 承受的風力為 c ,從 b a 承受的風力為 d
  • 【數據范圍】:
    • 1m20002n1000
    • 1abn1c,d1000

Sample Input

4 4
1 2 2 4
2 3 3 4
3 4 4 4
4 1 5 4

Output

如果無法完成減肥計划,則輸出NIE;

否則第一行輸出最小的承受風力的最大值,且第二行輸出一份方案(輸出的是邊的編號,第 i+1 行對應編號 i )。

Sample Output

4
4 3 2 1

Output Details

樣例如圖所示:

這里寫圖片描述


首先這一題套在外面的二分答案是非常明顯的,怎么說題目中也頻繁出現了“最大值最小”之類的字眼。二分枚舉這個最大風力,那么我們可以把剩下的圖提取出來。

根據題意要求:每條邊必須經過一次,而點可以多次經過,並且最后要回到原點。這樣的定義指向歐拉回路。於是本題的任務變成:

  • 對於重構的混合圖(既包含有向邊又包含無向邊)進行歐拉回路判定,再輸出對於最小符合題意風力重構的圖的歐拉回路方案。

圖論歐拉回路初步:

引入:(我這引入也真TM有個性

NOIP初賽有這樣一道題目:

  • [NOIP2007 提高組初賽 T9]歐拉圖 G 是指可以構成一個閉回路的圖,且圖 G 的每一條邊恰好在這個閉回路上出現一次(即一筆畫成)。在以下各個描述中,不一定是歐拉圖的是(D)。

    • A. 圖 G 中沒有度為奇數的頂點 。
    • B. 包含歐拉環游的圖。
    • C. 包含歐拉閉跡的圖。
    • D. 存在一條回路,通過每個頂點恰好一次。
    • E. 本身為閉跡的圖。

歐拉回路的基本概念順便解決上述問題):設圖 G=(V,E)

  • 歐拉路徑歐拉跡):圖 G 中經過每條邊一次並且僅一次的路徑成為歐拉路徑。
  • 歐拉回路歐拉閉跡):圖 G 中經過每條邊一次並且僅一次的回路成為歐拉路徑。
  • 歐拉圖:存在歐拉回路的圖稱為歐拉圖。
  • 半歐拉圖:存在歐拉路徑但不存在歐拉回路的圖稱為半歐拉圖。

注意D選項是錯誤的。(哈密頓圖:通過圖 G 的每個結點有且只有一次的回路,就是哈密頓回路,存在哈密頓回路的圖就是哈密頓圖)

歐拉圖的判定定理

  • 無向圖 G 為歐拉圖,當且僅當 G 為連通圖,且所有頂點的度為偶數
  • 有向圖 G 為歐拉圖,當且僅當 G 基圖連通,且所有頂點的入度等於出度。

歐拉圖的性質

  • C 是歐拉圖 G 中的一個簡單回路,將 C 中的邊從圖 G 中刪去得到一個新的圖 G ,則 G 的每個極大連通子圖都有一條歐拉回路。(極大/小連通圖:對於一個連通圖來說,極大連通圖即它本身,而極小連通圖指這個連通圖的生成樹)
  • C1 C2 是圖 G 的兩個沒有公共邊,但至少有一個公共頂點的簡單回路,我們可以將其合並成一個新的簡單回路 C

1)求歐拉回路

根據上述性質,得到求歐拉回路的算法(注意是在歐拉圖上):

  • 在圖 G 中任意找到一個回路 C
  • 將圖 G 中屬於回路 C 的邊刪除。
  • 在殘留圖的各極大子圖中分別尋找歐拉回路。
  • 將各極大連通子圖的歐拉回路合並到 C 中得到圖 G 的歐拉回路。
void Euler u //偽代碼
while(next v)
if e(u,v) unmarked
mark e(u,v),e(v,u)
Euler v
stack.push e(u,v)

上述偽代碼的時間復雜度 O(E) ,由於邊數過多會有爆棧危險,我們也會采用非遞歸形式。(待添加)

  • 以上內容引自《歐拉回路性質與應用探究》by 仇榮琦。

2)混合路歐拉路徑判定

  1. 對每條無向邊進行隨機定向

    通過隨機定邊,我們暫時構造出了一個有向圖,根據上述歐拉回路的有向圖判定定理,我們在接下來的處理中只要通過調整該有向圖中由無向邊變成的有向邊的方向,使得所有節點的入度=出度即可。

  2. 對當前的新圖構建網絡。

    我們按照當前點的出入度進行建邊:

    假設該點的 degree=u+u degree>0 表示需要更改從它連出去的 degree 條邊,同理 degree<0 表示需要更改從它連出去的 degree 條邊。

    再定義 e(u,v,1) 表示有值為1的流從 u v 流出,此時 edge(u,v) 被反向。同理,我們對於一個 degree>0 的點,為了讓其 degree 減小,我們假設一個虛擬源點 S ,此時若 e(S,u,degree2) 中的流量能全部跑光,則說明 u 后面的邊可以通過修改方向,使得 u 的出度=入度。

    那么我們對按照以下方式建邊:

    • 所有 degree>0 的點從 S 出發建邊,所有 degree<0 的點向 T 建邊。
    • 如果對於如果確定一條無向邊的方向為 edge(u,v) ,則建一條 (v,u,1) 的邊。
  3. 跑最大流算法,檢查是否滿流。(最大流初步)(待補充)

    檢查是否滿流時,按照上述定義只需要判斷所有與S相連的邊上是否滿流即可。


Code :(請無視我因為取名癌把我男神名字丟結構體名稱的舉動

#include <bits/stdc++.h>
#define M 2005
#define S 0
#define T 2004
#define inf 0x3f3f3f3f
using namespace std;
int n,m;
struct edge{int u,v,w1,w2,id;}Edges[M];

struct Union_Find_Set{
int fa[M],cnt;
void Init(){
for(int i=1;i<=n;i++)fa[i]=i;
cnt=n;
}
int Getfa(int x){
return (fa[x]==x)?fa[x]:fa[x]=Getfa(fa[x]);
}
void Union(int u,int v){
u=Getfa(u),v=Getfa(v);
if(u!=v)fa[u]=v,cnt--;
}
}Emiya;//判斷重構圖是否為連通圖

struct Max_Flow{
struct node{int v,f,nxt,id;}Nodes[M*M];
int head[M],tot,degree[M],level[M];
void Init(){
memset(head,-1,sizeof(head));
memset(degree,0,sizeof(degree));
tot=1;
}
void Add_edge(int u,int v,int w,int id){
Nodes[++tot]=(node){v,w,head[u],id};head[u]=tot;
Nodes[++tot]=(node){u,0,head[v],id};head[v]=tot;
}//鄰接表
bool bfs(){
memset(level,-1,sizeof(level));
level[S]=1;
queue<int>Q;Q.push(S);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int j=head[u];~j;j=Nodes[j].nxt)
if(Nodes[j].f&&!~level[Nodes[j].v]){
level[Nodes[j].v]=level[u]+1;
Q.push(Nodes[j].v);
if(Nodes[j].v==T)return true;
}
}
return false;
}
int Dinic(int u,int flow){
if(u==T)return flow;
int left=flow;
for(int j=head[u];~j&&left;j=Nodes[j].nxt)
if(Nodes[j].f&&level[u]+1==level[Nodes[j].v]){
int tmp=Dinic(Nodes[j].v,min(left,Nodes[j].f));
if(tmp){
left-=tmp;
Nodes[j].f-=tmp;
Nodes[j^1].f+=tmp;
}
}
if(left)level[u]=-1;
return flow-left;
}//最大流Dinic算法,Ford_Fulkerson或Edmonds_Karp算法亦可以套用

bool judge(int val){
Emiya.Init();Init();
for(int i=1;i<=m;i++){
int u=Edges[i].u,v=Edges[i].v;
if(Edges[i].w2<=val){//degree_in +1 degree_out -1
Emiya.Union(u,v);
Add_edge(u,v,1,Edges[i].id);
degree[u]++;
degree[v]--;
}else if(Edges[i].w1<=val){
Emiya.Union(u,v);
degree[u]--;
degree[v]++;
}
}
if(Emiya.cnt>1)return false;//原圖不連通
for(int i=1;i<=n;i++){
if(degree[i]&1)return false;//不滿足歐拉回路性質
if(degree[i]>0)Add_edge(S,i,degree[i]>>1,S);
else if(degree[i]<0)Add_edge(i,T,-degree[i]>>1,T);
}

while(bfs())Dinic(S,inf);//使勁跑網絡流,如果構成滿流則說明找到了歐拉回路
for(int j=head[S];~j;j=Nodes[j].nxt)if(Nodes[j].f)return false;
return true;
}
}Shirou;

int bisection(int R){
int L=1,res=-1;
while(L<=R){
int mid=L+R>>1;
if(Shirou.judge(mid)){//跑歐拉回路
R=mid-1;
res=mid;
}else L=mid+1;
}return res;
}

int head[M],tot=0;
struct New_Graph{int v,nxt,id;bool vis;}Graph[M<<1];
void Add_New_Edge(int u,int v,int id){
Graph[++tot]=(New_Graph){v,head[u],id,0};head[u]=tot;
}
bool f=false;
stack<int>stk;
void Euler_Print(int u){
for(int j=head[u];~j;j=Graph[j].nxt){
if(!Graph[j].vis){
Graph[j].vis=true;
Euler_Print(Graph[j].v);
stk.push(Graph[j].id);
}
}
}//打印解部分

int main(){
int num=0;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int u,v,w1,w2;
scanf("%d %d %d %d",&u,&v,&w1,&w2);
if(w1>w2)swap(u,v),swap(w1,w2);
Edges[i]=(edge){u,v,w1,w2,i};
if(num<w1)num=w1;
if(num<w2)num=w2;
}
int ans=bisection(num);//二分枚舉當前的最大風力
if(!~ans)puts("NIE");
else{
printf("%d\n",ans);
Shirou.judge(ans);
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)//沒有扔進網絡流的單向邊
if(Edges[i].w2>ans&&Edges[i].w1<=ans)
Add_New_Edge(Edges[i].u,Edges[i].v,Edges[i].id);
for(int u=1;u<=n;u++)//網絡流跑出來的滿流邊
for(int j=Shirou.head[u];~j;j=Shirou.Nodes[j].nxt){
Max_Flow::node now=Shirou.Nodes[j];
if(!now.f&&now.v!=S&&now.v!=T)Add_New_Edge(u,now.v,now.id);
}
Euler_Print(1);
while(!stk.empty()){
printf("%d%c",stk.top(),stk.size()==1?'\n':' ');
stk.pop();
}
puts("");
}
return 0;
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com