這是網絡流最基礎的部分——求出源點到匯點的最大流(Max-Flow)。
最大流的算法有比較多,本次介紹的是其中複雜度較高,可是比較好寫的EK算法。(不涉及分層,純粹靠BFS找匯點及回溯找最小流量獲得最終的答案)c++
EK算法,全名Edmonds-Karp算法(最短路徑增廣算法)。 算法
首先簡單介紹一下網絡流的基本術語:
源點:起點。全部流量皆今後點流出。只出不進。
匯點:終點。全部流量最後聚集於此。只進不出。
流量上限:有向邊(u,v)(及弧)容許經過的最大流量。
增廣路:一條合法的從源點流向匯點的路徑。
計算最大流就是不停尋找增廣路找到最大值的過程。數組
合法的網絡流具備如下性質:
1.f(i,j) <= c(i,j);//任意有向邊的流量必定小於等於該邊的流量限制
2.f(i,j) = -f(j,i);//從i流向j的流量與j流向i的流量互爲相反數(反向邊)
3.out(i) = in(i);//除源點、匯點外,任意一點i流入的流量與流出的相等(只是路過)markdown
(截自洛谷)網絡
EK算法思路:
1.經過BFS拓展合法節點(每一個節點在本次BFS中僅遍歷一次),找到匯點,並記錄每一個節點的前面節點(pre)(若找不到增廣路,算法結束)
2.經過BFS的記錄,從匯點回溯回源點,記錄下每條弧流量的**最小值**minn, ans += minn(不然就會超出某條邊的限制流量)
3.將全部通過的邊的流量減去minn,反向邊加上minn
4.重複上述步驟,直到找不到增廣路,算法結束。函數
樸素版EK:
最爲簡單的寫法,經過鄰接矩陣存儲。
優勢:代碼簡單,一目瞭然。
缺點:輕易爆內存,(N^2)的空間太大,N >10000基本就廢了(MLE)。
源代碼:測試
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f
#define maxn 10005
int n, m, st, en, flow[maxn][maxn], pre[maxn];
int q[maxn], curr_pos, st_pos, end_pos;
bool wh[maxn];
int max_flow;
void Init()//初始化
{
int i, a, b, c;
scanf("%d%d%d%d", &n, &m, &st, &en);
for(i = 0; i != m; ++i)
{
scanf("%d%d%d", &a, &b, &c);
flow[a][b] += c;
}
return ;
}
bool Bfs(int st, int en)//廣搜找源點
{
st_pos = -1, end_pos = 0;
memset(wh, 0, sizeof wh);
wh[st] = 1;
q[0] = st;
while(st_pos != end_pos)
{
curr_pos = q[++st_pos];
for(int i = 1; i != n+1; ++i)
{
if(!wh[i] && flow[curr_pos][i] > 0)
{
wh[i] = 1;
pre[i] = curr_pos;
if(i == en)
{
return true;
}
q[++end_pos] = i;
}
}
}
return false;
}
int EK(int start_pos, int end_pos)
{
int i, minn;
while(Bfs(start_pos, end_pos))//回溯
{
minn = INF;
for(i = end_pos; i != start_pos; i = pre[i])
{
minn = min(minn, flow[pre[i]][i]);
}
for(i = end_pos; i != start_pos; i = pre[i])
{
flow[pre[i]][i] -= minn;
flow[i][pre[i]] += minn;//反向弧加上該值(具體緣由下文詳解)
}
max_flow += minn;
}
return max_flow;
}
int main()
{
//freopen("test.in", "r", stdin);
//freopen("test.out", "w", stdout);
Init();
printf("%d", EK(st, en));
//fclose(stdin);
//fclose(stdout);
}
通過洛谷測評(P3376 【模板】網絡最大流),50分,MLE五個點。優化
關於反向邊加上最小流值的緣由:
若是存在a->b有流,那麼若是某一點c->b有流,則流量實際上能夠再從b流到a,能夠理解爲把本來a->b的流量懟了回去。
BFS遍歷獲得的剩下部分的增廣路的流量其實是本來a->b的流,而本來a->b以後流向匯點的增廣路的部分流量變爲由c->b的流量。
增廣路依舊合法。spa
這證實了鄰接矩陣是不可行的,因而咱們想到了麻煩一點可是省空間的存儲方法——邊表。至少從m*m優化到n*m,實則一般n*(m/10)便足夠。code
定義一個結構體
struct qi{
int st, en, num;//弧的始點,終點,權值
}flow[maxm];
相對應的廣搜也要稍做調整,注意一下pre的存儲方法:
pre[i]=k,i是點,k是弧的編號。
bool Bfs(int st, int en)
{
st_pos = -1, end_pos = 0;
memset(wh, 0, sizeof wh);
wh[st] = 1;
q[0] = st;
while(st_pos != end_pos)
{
curr_pos = q[++st_pos];
for(int i = 0; i < 2*m; ++i)
{
if(flow[i].st == curr_pos && flow[i].num > 0 && !wh[flow[i].en])
{
ne = flow[i].en;
wh[ne] = 1;
pre[ne] = i;
if(ne == en)
{
return true;
}
q[++end_pos] = flow[i].en;
}
}
}
return false;
}
因此Tracback的遍歷循環也要改變。
for(i = end_pos; i != st; i = flow[pre[i]].st)//注意一下i如何取下一個的
{
minn = min(minn, flow[pre[i]].num);
}
事實證實:MLE的問題完美解決,測試結果:94ms , 18785kb,TLE三個點。
那麼還須要優化一下時間。
注意BFS拓展時的循環:i:=0->2*m//m是弧的個數(由於有反向邊,故是2*m)
因爲題目範圍是m <= 100000,每次最大循環達到2e5,太浪費了。
因而咱們在邊表再作一點手腳:開二維數組對每一個始點全部的弧分別記錄。
x[i][m]指以i爲始點的第m條弧(再開一個num[i]記錄每一個始點弧的個數)。
那麼代碼就更加繁瑣了。
首先是讀入優化:
int read()
{
input = 0;
a = getchar();
while(a < '0' || a > '9')
a = getchar();
while(a >= '0' && a <= '9')
{
input = input*10+a-'0';
a = getchar();
}
return input;
}
而後是初始化:
void Init()
{
int i, a, b, c;
memset(num, -1, sizeof num);
n = read(), m = read(), st = read(), en = read();
for(i = 0; i != m; ++i)
{
flow[i].st = read();
flow[i].en = read();
flow[i].num = read();
re[flow[i].st][++num[flow[i].st]] = i;
re[flow[i].en][++num[flow[i].en]] = i+m;//初始化時將第i條弧的反向邊編號爲i+m
}
for(int i = m; i != 2*m; ++i)
{
flow[i].st = flow[i-m].en;
flow[i].en = flow[i-m].st;
flow[i].num = 0;
}
pre[st] = -1;
return ;
}
BFS部分:
bool Bfs(int st, int en)
{
int i, j;
st_pos = -1, end_pos = 0;
memset(wh, 0, sizeof wh);
wh[st] = 1;
q[0] = st;
while(st_pos != end_pos)
{
curr_pos = q[++st_pos];
for(i = 0; i < num[curr_pos]+1; ++i)
{
j = re[curr_pos][i];//當前可拓展節點的候選節點,即當前節點的第i條弧的終點
if(flow[j].st == curr_pos && flow[j].num > 0 && !wh[flow[j].en])
{
ne = flow[j].en;
wh[ne] = 1;
pre[ne] = j;
if(ne == en)
{
return true;
}
q[++end_pos] = flow[j].en;
}
}
}
return false;
}
Traceback只需改變反向邊部分:
flow[pre[i]+m].num += minn;
主函數部分不須要調整。
最終測評結果:392ms , 56988kb,AC。
經過上述過程,咱們得知網絡流的存儲結構應該爲邊表而非鄰接矩陣。固然,使用vector來存儲弧也是能夠的,這樣會更大程度上節省空間,但會對時間形成必定影響,根據實際狀況自行取捨。
最後附上完整代碼:
/*
Max_flow- EK
2017/04/07
While(BFS can find the end)
for i,i->j:=st -> end:
f[i][j] -= minn;f[j][i] += minn; ans += minn;//so we can delete at least one edge(the one which satisfies f[i][j] == minn)
*/
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f
#define maxm 200005
#define maxn 10005
struct qi{int st, en, num;}flow[maxm];
int n, m, st, en, pre[maxn], re[maxn][maxn/10], num[maxn];
int q[maxn], curr_pos, st_pos, end_pos, ne, max_flow;
bool wh[maxn];
int input;
char a;
int read()
{
input = 0;
a = getchar();
while(a < '0' || a > '9')
a = getchar();
while(a >= '0' && a <= '9')
{
input = input*10+a-'0';
a = getchar();
}
return input;
}
void Init()
{
int i, a, b, c;
memset(num, -1, sizeof num);
n = read(), m = read(), st = read(), en = read();
for(i = 0; i != m; ++i)
{
flow[i].st = read();
flow[i].en = read();
flow[i].num = read();
re[flow[i].st][++num[flow[i].st]] = i;
re[flow[i].en][++num[flow[i].en]] = i+m;
}
for(i = m; i != 2*m; ++i)
{
flow[i].st = flow[i-m].en;
flow[i].en = flow[i-m].st;
}
return ;
}
bool Bfs(int st, int en)
{
int i, j;
st_pos = -1, end_pos = 0;
memset(wh, 0, sizeof wh);
wh[st] = 1;
q[0] = st;
while(st_pos != end_pos)
{
curr_pos = q[++st_pos];
for(i = 0; i < num[curr_pos]+1; ++i)
{
j = re[curr_pos][i];
if(flow[j].st == curr_pos && flow[j].num > 0 && !wh[flow[j].en])
{
ne = flow[j].en;
wh[ne] = 1;
pre[ne] = j;
q[++end_pos] = flow[j].en;
if(ne == en)
return true;
}
}
}
return false;
}
int EK(int start_pos, int end_pos)
{
int i, minn;
while(Bfs(start_pos, end_pos))
{
minn = INF;
for(i = end_pos; i != st; i = flow[pre[i]].st)
{
minn = min(minn, flow[pre[i]].num);
}
for(i = end_pos; i != st; i = flow[pre[i]].st)
{
flow[pre[i]].num -= minn;
flow[pre[i]+m].num += minn;
}
max_flow += minn;
}
return max_flow;
}
int main()
{
//freopen("test.in", "r", stdin);
//freopen("test.out", "w", stdout);
Init();
printf("%d", EK(st, en));
//fclose(stdin);
//fclose(stdout);
}
自此,EK算法的講解便結束了。
箜瑟_qi 2017.04.07 2:32
2017.04.21略加修改:
/*
Max_flow- EK
2017/04/07
*/
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f
#define maxm 200005
#define maxn 10005
struct Edge{
int st, en, num;
Edge(){}
Edge(int s, int e, int n):
st(s), en(e), num(n){}
}flow[maxm];
int n, m, st, en, pre[maxn], re[maxn][maxn/10], num[maxn];
int q[maxn], curr_pos, st_pos, end_pos, ne, max_flow;
bool wh[maxn];
int input;
char a;
static int read()
{
a = getchar();
input = 0;
while(a < '0' || a > '9') a = getchar();
while(a >= '0' && a <= '9')
{
input = input*10+a-'0';
a = getchar();
}
return input;
}
static void Init()
{
int i, a, b, c, cur;
memset(num, -1, sizeof num);
n = read(), m = read(), st = read(), en = read();
for(i = 0; i != m; ++i)
{
cur = 2*i;
a = read(), b = read(), c = read();
flow[cur] = Edge(a, b, c);
flow[cur^1] = Edge(b, a, 0);
re[flow[cur].st][++num[flow[cur].st]] = cur;
re[flow[cur].en][++num[flow[cur].en]] = cur^1;
}
return ;
}
static bool Bfs(int st, int en)
{
int i, j;
st_pos = -1, end_pos = 0;
memset(wh, 0, sizeof wh);
wh[st] = 1;
q[0] = st;
while(st_pos != end_pos)
{
curr_pos = q[++st_pos];
for(i = 0; i < num[curr_pos]+1; ++i)
{
j = re[curr_pos][i];
if(flow[j].st == curr_pos && flow[j].num > 0 && !wh[flow[j].en])
{
ne = flow[j].en;
wh[ne] = 1;
pre[ne] = j;
q[++end_pos] = flow[j].en;
if(ne == en)
return true;
}
}
}
return false;
}
static int EK(int start_pos, int end_pos)
{
int i, minn;
while(Bfs(start_pos, end_pos))
{
minn = INF;
for(i = end_pos; i != st; i = flow[pre[i]].st)
{
minn = min(minn, flow[pre[i]].num);
}
for(i = end_pos; i != st; i = flow[pre[i]].st)
{
flow[pre[i]].num -= minn;
flow[pre[i]^1].num += minn;
}
max_flow += minn;
}
return max_flow;
}
int main()
{
//freopen("test.in", "r", stdin);
//freopen("test.out", "w", stdout);
Init();
printf("%d", EK(st, en));
//fclose(stdin);
//fclose(stdout);
}