最大流背景介绍:比如城市水管,从水站运水送你家,许多管道总共能同时送多少水到
最大流分三个算法,算法难度与优越性逐步提升:
3,ISAP算法
1,算法介绍
设起点s,终点t
一条能够从s到t的路(当然,每个点还有剩余容量可以运水)被称为增广路:看到剩余,你应该会联想到,如果这条路有一个点容量用完了,那这条路就断了。
所以我们可以用bfs一直遍历:只要找到这样一条路径,我们就把路径上容量最小的点取出来,然后路径上所有点减去这个最小值,我们一直找路径并减去路径点容量,直到不再有增广路,就求解出最大流了
相信你会发现亿点小瑕疵,如果我有一条路径不是最优解,那不就寄了,对的,所以人是容易后悔的(机器也想要嘛)
因此,我们提出一个重要想法:反向边
反向边初始时为0,但如果他的正边减多少,那么他就加多少
给出ek算法的几个经典图
对于这么一张图,我们假设先走1->2->3->4
但是,我们发现,实际上走1->2->4加上1->3->4更优
当你使用反向边你会发现,走完1->2->3->4后,多了一条1->3->2->4的路,实际等价于走1->2->4加上1->3->4(走两遍对于没走)
2,使用工具:
改版链式向前星:可以参考三大图的储存——1,邻接矩阵,2,邻接表,3,链式向前星
(我们如何用链式向前星表示正向边于反向边的关系呢,可以用异或,发现2与3,4与5,......可以互相异或1得到,所以我们建边时两个一起建(注意,num不再从1开始,从2开始,否则你的1实际没有匹配的0)
3,一道模板题介绍代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const int N = 500;
int n, m, s, t;
int head[N];
bool vis[N];//标记数组
ll dis[N];//记录点的容量
int pre[N];//存储bfs走过路径点(代表的num值),用于回溯修改容量
int num = 1;//一定记得初始化1,不是0
ll ans;//记录结果
struct node
{
ll w, next, to;
} edge[1000000];
void add(ll u, ll v, ll w)
{
edge[++num].next = head[u];//加正向边
edge[num].to = v;
edge[num].w = w;
head[u] = num;
edge[++num].next = head[v];//加反向边
edge[num].to = u;
edge[num].w = 0;//边值初始化为0
head[v] = num;
}
int bfs()
{
memset(vis, 0, sizeof(vis));//每次清空标记
queue<int>q;
q.push(s);
vis[s] = 1; //起点记得不要忘了在外面标记
dis[s] = INF;//起点初始赋值极大
while (!q.empty())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = edge[i].next)
{
int v = edge[i].to;
ll w = edge[i].w;
if (w == 0||vis[v])continue; //不要忘了排除值为0的边,标记过也跳过
vis[v] = 1;//记得标记1
dis[v] = min(dis[x], w);//选取最小值,一直遍历到终点t,最终获得的就是路径上的点中的最小容量
pre[v] = i; //记录连接v点的x点的num值,用于回溯
if (v == t)return 1;//如果v=t,那就是成功到终点了,是一条增广路,返回1
q.push(v);//否则存入继续遍历
}
}
return 0;//最终不要完了找不到返回0
}
void update()//回溯修改容量
{
int v = t;//从终点回溯到起点,v记录是哪个点,tmp记录num值
while (v != s)
{
int tmp = pre[v];//获得前面连接v点值的点的num
edge[tmp].w -= dis[t];//正向边减最小容量(遍历最后就是dis[t])
edge[tmp ^ 1].w += dis[t];//方向边就增加//注意,这里正反向是相对的
v = edge[tmp ^ 1].to;//通过反向边回到更前面的点
}
ans += dis[t];//不要忘出来了ans累积n减少的容量
}
int main()
{
cin >> n >> m >> s >> t;
ll u, v, w;
for (int i = 1; i <= m; ++i)
{
cin >> u >> v >> w;
add(u, v, w);
}
while (bfs())//只要找得到增广路,就累积ans
{
update();
}
cout << ans << endl;
return 0;
}
4,评估
时间复杂度为O(nm^2)O(nm2),一般能处理10^3~10^4规模的网络
缺点:EK算法每次都可能会遍历整个残量网络,但只找出一条增广路