图论 —— 网络流 —— 最大流 —— 压入与重标记算法

【概述】

压入与重标记算法(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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值