每次从残留网络中找出一条从源结点到汇结点的最短路径,流选为路径中的残存容量,依据流更新残存网络(将每条边的残存容量改为当前容量减去流的大小,并添加对应的反向边,边的容量为流的大小)
重复选最短路径,更新残存网络,直到没有最短路径为止
此时的流累加和即为最大流
此算法的精华也就在于用建立反向边而代替了回溯的过程,大大减少了时间复杂度,
为何建立反向边正确呢?
如果增广路中有反向边,其实就是把该正向边边对应的流量“退“了回去,使边拥有”反悔“的机会。
例题:POJ 1273 http://poj.org/problem?id=1273
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
//#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
//typedef __int128 bll;
const ll maxn = 105+100;
const ll mod = 1e9+7;
const ld pi = acos(-1.0);
const ll inf = 1e18;
const ld eps = 1e-5;
//const ld e = exp(1);
ll n,m;
ll G[maxn][maxn],pre[maxn]; //G用来保存残余网络,per存储当前节点的前驱节点
bool flag[maxn]; //判断是否在队列中
bool bfs(ll be,ll en) //寻找增广路
{
queue<ll>Q;
memset(pre,-1,sizeof(pre));
memset(flag,false,sizeof(flag));
Q.push(be);
flag[be] = true;
pre[be] = be;
while(!Q.empty())
{
ll tmp = Q.front();
Q.pop();
if(tmp == en)
return true;
for(ll i = 1; i <= n; i++)
{
if(flag[i] == false && G[tmp][i] > 0)
{
flag[i] = true;
pre[i] = tmp;
Q.push(i);
}
}
}
return false;
}
ll EK(ll be,ll en)
{
ll flow = 0,mi;
while(bfs(be,en))
{
mi = inf;
for(ll i = en; i != be; i = pre[i])
{
mi = min(mi,G[pre[i]] [i] ); //寻找最小值
}
for(ll i = en; i != be; i = pre[i])
{
G[ pre[i] ][i] -= mi; //更新正向边
G[i][ pre[i] ] += mi; //建立反向边
}
flow += mi;
}
return flow;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
while( cin >> m >> n )
{
memset(G,0,sizeof(G));
//cin >> m >> n;
for(ll i = 1; i <= m; i++)
{
ll x,y,z;
cin >> x >> y >> z;
G[x][y] += z; //有可能一条边出现多次
}
cout << EK(1,n) << endl;
}
return 0;
}