【概述】
压入与重标记算法(Push-Relable Algorithms)又称预留推进算法,是 GoldBerg 与 Tarjan 共同发现的,时间复杂度为 O(V*V*E)
该算法与 EK 、Dinic 等增广路算法一样是用于求最大流的,不同于增广路算法检查整个残留网络来找出增广路径,该算法每次仅对一个顶点进行操作,并且检查残量网络中该点相邻的点。
【算法原理】
首先引入新名词的定义:
- 余流:对于一个顶点,依旧存储在该点中,尚未排除的流量,记作:excess(u)= f(V,v)>=0
- 活性点:对于一个不属于源点、汇点的任意点 v,若其容量有限且其余流大于 0,那么称该点为一活性点,且该点流量溢出
- 点的高度:每个点 i 被赋予了一个高度 height(i),余流只能从高向下压入比他低的顶点
- 合法标记:对于图中的任意一条边 (u,v),若满足 height(u)<=height(v)+1,那么具有一个合法标记,而仅当 height(u)=height(v)+1 时,才将流从 u 压入 v
假设每个顶点 u 存在无限大容量的水库,可以用来储存任意多的水,储存在该顶点的流量即为该点的余流
在开始时,令除 S、T 两点外的点的高度为 height(S)=|V|,height(T)=0 固定不变,其他点的高度 height(i) 初始化为 0,随着算法进行不断进行调整
令所有从源点 S 出来的边,达到饱和状态,成为饱和边,然后尽量使这个余流达到汇点 T,若是有一部分无法到达 T,那么将剩下的余流返回源点 S,当任意一点 u 的余流 excess(u)=0 时,算法停止,返回最大流
每个点与其关联的的点处于同一高度,当需要压入点 u 的流量时(Push(u) 操作),重标记点 u 的高度 height(u)(relabel(u) 操作),使得点 u 的余流流向高度小于他的点 v,直到存在一点的余流为 0
【实现】
int cap[N][N]; //容量网络
int flow[N][N]; //流量网络
int height[N]; //高度标记
int excess[N]; //余流
void push(int start, int end) {
int newflow = min(excess[start], cap[start][end] - flow[start][end]);
//改变当前流量
flow[start][end] += newflow;
flow[end][start] = -flow[start][end];
//改变余流
excess[start] -= newflow;
excess[end] += newflow;
}
bool reLabel(int S, int T, int index) { //重标记
int minn = INF; //最小的高度
for (int i = S; i <= T; i++)
if (cap[index][i] - flow[index][i] > 0)
minn = min(minn, height[i]);
if (minn == INF)
return false;
height[index] = minn + 1; //满足height(u)=height(v)+1,标记下放
for (int i = S; i <= T; i++) {
if (excess[index] == 0) //余流为0,标记下放结束,返回true
return true;
if (height[i] == minn &&
cap[index][i] > flow[index][i]) //处于同一高度,压入流量
push(index, i);
}
return true;
}
void pushReLabel(int S, int T) {
//预处理余流
for (int i = S; i <= T; i++) {
if (cap[S][i] > 0) {
int newflow = cap[S][i];
flow[S][i] += newflow;
flow[i][S] = -flow[S][i];
excess[S] -= newflow;
excess[i] += newflow;
}
}
bool flag = true;
while (true) {
if (!flag)
break;
flag = false;
for (int i = S; i <= T - 1; i++) {
if (excess[i] > 0) { //当余流大于0时,继续重标记
if (flag || reLabel(S, T, i))
flag = true;
}
}
}
}
int main() {
int n, m;
while (scanf("%d%d", &n, &m) != EOF) {
memset(cap, 0, sizeof(cap));
memset(flow, 0, sizeof(flow));
memset(height, 0, sizeof(height));
memset(excess, 0, sizeof(excess));
int S = 1, T = n;
height[S] = n;
height[T] = 0;
for (int i = 1; i <= m; ++i) {
int x, y, w;
scanf("%d%d%d", &x, &y, &w);
cap[x][y] = w;
}
pushReLabel(S, T);
int res = excess[T]; //汇点的余流即最大流
printf("%d\n", res);
}
return 0;
}