前一段陣子學了極小的一部分網絡流,這裏做一些總結,主要還是給自己看的a
最大流:
題幹描述:
給出一個網絡圖,以及其源點和匯點,求出其網絡最大流。
輸入格式:
第一行包含四個正整數N、M、S、T,分別表示點的個數、有向邊的個數、源點序號、匯點序號。
接下來M行每行包含四個正整數ui、vi、wi、fi,表示第i條有向邊從ui出發,到達vi,邊權爲wi(即該邊最大流量爲wi),單位流量的費用爲fi。
輸出格式:
一行,包含兩個整數,依次爲最大流量和在最大流量情況下的最小費用。
開始正題了:
先貼一下代碼:
EK:
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> using namespace std; const int inf = 1 << 30; int n,m,s,t; struct Node { int v; int val; int next; }node[200866]; struct Pre { int v; //該點的前一個點 int edge; //與該點相連的邊 }pre[100866]; int top = 1,head[100866]; //top必須從一個奇數開始 inline void addedge(int u,int v,int val) { node[++top].v = v; node[top].val = val; node[top].next = head[u]; head[u] = top; } inline int read() { int x = 0; char ch = getchar(); while(ch > '9' || ch < '0') ch = getchar(); while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x; } int inque[100866];//點是訪問過裏 inline bool bfs() //是否有增廣路 { queue<int>q; memset(inque,0,sizeof(inque)); memset(pre,-1,sizeof(pre)); inque[s] = 1; q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); for(int i=head[u];i;i=node[i].next) { int d = node[i].v; if(!inque[d] && node[i].val) { pre[d].v = u; pre[d].edge = i; if(d == t) return 1; inque[d] = 1; q.push(d); } } } return 0; } int EK() { int ans = 0; while(bfs()) { int minn = inf; for(int i=t;i!=s;i=pre[i].v) minn=min(minn,node[pre[i].edge].val);//增廣路上最小的邊的權值 for(int i=t;i!=s;i=pre[i].v) { node[pre[i].edge].val -= minn; node[pre[i].edge ^ 1].val += minn; //反向的邊的編號是正向邊的編號^1 } ans += minn; } return ans; } int main() { n = read(); m = read(); s = read(); t = read(); int u,v,w; for(int i=1;i<=m;i++) { u = read(); v = read(); w = read(); addedge(u,v,w); addedge(v,u,0); } printf("%d",EK()); return 0; }
Dinic:
按(+1)找反向邊,個人認爲不大靠譜
#include<cstdio> #include<queue> #include<cmath> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 20086 using namespace std; const int inf = 1e9; int n,m,s,t; int cnt,max_flow; int dep[MAXN]; //記錄每一個節點的深度 int head[MAXN]; //存圖 int cur[MAXN]; //記錄路徑的 queue<int> q; struct Node { int len; int next; int to; }edge[MAXN * 10 << 1]; inline void add_edge(int u,int v,int w) //加邊建圖 { edge[++cnt].to = v; edge[cnt].len = w; edge[cnt].next = head[u]; head[u] = cnt; } inline bool bfs(int s,int t) //判斷是不是可以跑 { memset(dep,0x7f,sizeof(dep)); while(!q.empty()) q.pop(); for(int i=1;i<=n;i++) cur[i] = head[i]; //初始化 dep[s] = 0; //起點開始 q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); for(int i=head[u];i;i=edge[i].next) { int v = edge[i].to; if(dep[v] > inf && edge[i].len) //正向邊,可以跑 { dep[v] = dep[u] + 1; q.push(v); } } } if(dep[t] < inf) return true; else return false; } inline int dfs(int now,int t,int limit) //limit就是一個限制 //dfs的優點:在一次增廣的過程中,尋找多條增廣的路徑 { if(!limit || now == t) return limit; int flow = 0; int f; for(int i=head[now];i;i=edge[i].next) { cur[now] = i; //i是邊的編號,更改流道的路徑 int v = edge[i].to; if(dep[v] == dep[now] + 1 && (f = dfs(v,t,min(limit,edge[i].len)))) { flow += f; limit -= f; edge[i].len -= f; edge[i + 1].len += f; if(!limit) break; } } return flow; } void Dinic(int s,int t) { while(bfs(s,t)) max_flow += dfs(s,t,inf); } int main() { scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,0); } Dinic(s,t); printf("%d",max_flow); return 0; }
按(^1)找反向邊,注意cnt初始化爲-1
#include<cstdio> #include<queue> #include<cmath> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 20086 using namespace std; const int inf = 1e9; int n,m,s,t; int u,v,w; int cnt = -1; //爲了 ^ 的找反向邊做準備 int max_flow; int dep[MAXN]; //記錄每一個節點的深度 int head[MAXN]; //存圖 int cur[MAXN]; //記錄路徑的 queue<int> q; struct Node { int len; int next; int to; }edge[MAXN * 10 << 1]; inline void add_edge(int u,int v,int w) //加邊建圖 { edge[++cnt].to = v; edge[cnt].len = w; edge[cnt].next = head[u]; head[u] = cnt; } inline bool bfs(int s,int t) //判斷是不是可以跑 { memset(dep,0x7f,sizeof(dep)); while(!q.empty()) q.pop(); for(int i=1;i<=n;i++) cur[i] = head[i]; //初始化 dep[s] = 0; //起點開始 q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); for(int i=head[u];i;i=edge[i].next) { int v = edge[i].to; if(dep[v] > inf && edge[i].len) //正向邊,可以跑 { dep[v] = dep[u] + 1; q.push(v); } } } if(dep[t] < inf) return true; else return false; } inline int dfs(int now,int t,int limit) //limit就是一個限制 //dfs的優點:在一次增廣的過程中,尋找多條增廣的路徑 { if(!limit || now == t) return limit; int flow = 0; int f; for(int i=head[now];i;i=edge[i].next) { cur[now] = i; //i是邊的編號,更改流道的路徑 int v = edge[i].to; if(dep[v] == dep[now] + 1 && (f = dfs(v,t,min(limit,edge[i].len)))) { flow += f; limit -= f; edge[i].len -= f; edge[i ^ 1].len += f; if(!limit) break; } } return flow; } void Dinic(int s,int t) { while(bfs(s,t)) max_flow += dfs(s,t,inf); } int main() { scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); add_edge(u,v,w); add_edge(v,u,0); } Dinic(s,t); printf("%d",max_flow); return 0; }
原理
對於網絡流,我們粗暴地每一個節點的SPFA顯然是較劣的
所以我們需要一種算法來得到最大流量
首先介紹一下EK,同時它也是後面Dinic的基礎
EK的着眼點就在於我們知道跑SPFA是顯然不行的,那麼我們就可以針對這一點進行優化
優化的措施就是建立反向邊
反向邊的含義就是說給了一次反悔的機會,讓我們可以退回到當前狀態之前的狀態
這也是EK算法的核心,既然我們不能直接跑,那麼我們就可以在建圖的過程中針對於每一條邊都建立一條它所對應的反向邊
我們就可以通過反向邊進行回溯,從而得到通過每一個節點的最優解法(同時,這個的魯棒性可以幫助我們解決一些Dinic解決不了的問題)
EK的跑法就是在SPFA的過程中將每一種狀態的最優結果找出來,從而得到一個這些結果裏面最大的收益
以上黃線標註的地方就是Dinic針對於EK所改進的地方
因爲在跑EK的時候做了大量的"無用功",那麼我們可不可以減少BFS的次數呢?
Dinic就通過DFS將BFS的次數縮短了
這裏我們把BFS更改爲bool類型,來判斷可不可以向下流
初始化dep[T] = 0x7f
然後判斷假如可以流的話,那麼dep[T]肯定是小於這個數值的(哪有圖0x7f深度的)
然後我們可以知道這個流可不可以向下流動
然後我們就可以在bfs可行的基礎上進行dfs了
dfs的優點:在一次增廣的過程中,尋找多條增廣的路徑
這樣肯定是優於全跑一遍的
完結