网络流

目录

 

概念

最大流最小割定理

Ford-Fulkerson

dfs 邻接矩阵

dfs 前向星

Edmond-Karp

Dinic算法

ISAP

递归+多路增广

非递归+单路增广

HLPP

终极优化


概念

网络流G=(V,E)是一个有向图,每条边都有一个非负的容量c\left(u,v \right )\geq 0 ,如果\left(u,v \right ) \notin E,那么c\left(u,v \right ) =0

网络流中有两个特殊的点,源点s,汇点t

为了方便,我们假设G是连通图,并且\left | E \right | \geq \left | V \right | -1

:G的流是实值函数f,满足下面3个性质

      容量限制:\forall u,v \in V , f\left(u,v \right ) \leq c\left(u,v \right )

      反对称性:\forall u,v \in V, f\left(u,v \right ) =-f\left(v,u \right )

      流守恒性:\forall u \in V-\left{s,t \right } , \sum_{v\in V} f\left(u,v \right ) =0

     

     所以流的值定义为 \left | f \right | = \sum_{v \in V} f\left(s,v \right )

     

  用通俗点的话讲,就是,源点可以当作水库,边当作水管,然后将水流到汇点,问能流多

  容量限制:流的量不能超过水管的大小

  流守恒:简单来说就是除了s和t,流入多少就要流出多少,不能多不能少

  流的值:源点流出了多少,或者汇点接收了多少,也可以叫做流量

容量网络:由容量组成的网络流,也就是原来的网络流
流量网络:由流组成的网络流

可行流:满足流的条件的网络流称之为可行流

最大流:可行流中,流量最大的

链:流量网络中从源点到汇点的路径

前向弧:链中,边与容量网络中方向一致的边

后向弧:链中,边与容量网络中方向相反的边

增广路:链中,前向弧的流量<=容量,后向弧流量>0

 

:将网络流中,顶点分为2个集合

S-T割:割的两个集合S,T,s \in S,t \in T,比如上面那个图,源点A,汇点C,则S={A,D},T={C,B}就是一个S-T割

割的容量:S-T割中,u\in S,v \in T,前向弧的容量和

最小割:割的容量中最小的

割的流量:S-T割中,u\in S,v \in T,前向弧的流量和-后向弧的流量和

 

还是上面那个图源点A,汇点C,S={A,D},T={C,B},

那割的容量就是c(A,B)+c(D,C)=4+8

   割的流量就是f(A,B)+f(D,C)-f(D,B)=4+8-5

所以割的流量=割的容量,当且仅当S-T割中没有后向弧

最大流最小割定理

命题1:对于可行流的任意一个割,割的流量=可行流的流量

证明:

    假设T={t},S=V-{t},显然可行流流量就是流入汇点的流量,割的流量也是,所以相等

    每当我们从S中取出一个点加入T的时候,比如V5,那这时候会减去V5到T集合的流量,但是会加上到V5的流量,由于流量时守恒的,所以割的流量=可行流的流量

    根据归纳,可以知道,这个命题成立

比如这张图,流量是19

第一次S={1,2,3,5},T={4,6},割的流量=12-4+7+4=19

第二次S={1,3,5},T={2,4,6},割的流量=11+1-4+7+4=19

 

命题2:可行流的流量一定小于等于任意一个割的容量

证明:可行流的流量=割的流量<=割的容量

 

命题3:对于可行流G,设其流量为f,如下三个命题等价:

1.存在一个割使得割的容量c=f
2.f是最大流的流量
3.G中不存在任何增广路

证明:

1推2: 假设c不是最小割,则存在c0<c,但是根据可行流的流量=割的流量<=割的容量,f<c0,也就是c=f<c0矛盾,所以c是最小割,又因为达到最小割时,割的流量=割的容量

可行流的流量为最大流

2推3:逆否命题:G中存在增广路,f不是最大流的流量

如果存在增广路,则f可以继续增大,显然不是最大流

3推1:

   假设不存在增广路了,设S为源点s通过非饱和前向弧(这条前向弧流量<容量)或者非零反向弧能到达的点,T=V-S,这构成了一个S-T割,\forall u \in S, v \in T,f\left(u,v \right )=c\left(u,v \right ),如果f\left(u,v \right ) < c\left(u,v \right ),则有

这条边为非饱和前向弧或者非零反向弧,那v就属于S,与v属于T矛盾,于是命题得证

 

没特殊说明,代码根据洛谷P3376写的

Ford-Fulkerson

准确来说,这是一个方法不是具体的算法

主要思路,找增广路,更新边,但是这个方法并没有规定怎么找增广路

如果你用dfs,时间复杂度O((V+E)*maxflow)

dfs 邻接矩阵

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 205;
const long long int MAX = 0x7FFFFFFFFFFFFFFF;
long long int edge[N][N];//邻接矩阵
bool visit[N];
int n, s, t;//总数,源点,汇点
/**
 * 深度优先
 * @param u 当前点
 * @param flow 能通过的流量
 * @return 返回流量,无法到达汇点则返回0
 */
long long int dfs(int u, long long int flow) {
    if (u == t)return flow;
    visit[u] = true;
    for (int i = 1; i <= n; ++i) {
        if (visit[i] || 0 == edge[u][i])continue;//访问过,或者没有残余流量
        long long int result = dfs(i, edge[u][i] < flow ? edge[u][i] : flow);
        if (result > 0) {
            //正向边
            edge[u][i] -= result;
            //反向边
            edge[i][u] += result;
            return result;
        }
    }
    return 0;
}
/**
 * 最大流
 * @return 最大流
 */
long long int Ford_Fulkerson() {
    if (s == t)return 0;
    long long int result = 0;
    long long int ans = dfs(s, MAX);
    while (ans > 0) {
        result += ans;
        memset(visit, false, sizeof(visit));
        ans = dfs(s, MAX);
    }
    return result;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v;
        long long int c;
        scanf("%d%d%lld", &u, &v, &c);
        edge[u][v] += c;
    }
    printf("%lld\n", Ford_Fulkerson());
    return 0;
}

dfs 前向星

我写的好像有点问题,小心T掉

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 205, M = 10005;
const long long int MAX = 0x7FFFFFFFFFFFFFFF;
//前向星
struct Edge {
    int v, next;
    long long int c;
}edge[M];
int head[N];//前向星头
bool visit[N];
int n, s, t, cnt = 1;
/**
 * 前向星添加边
 * @param u 起点
 * @param v 终点
 * @param c 容量
 */
void add_edge(int u, int v, long long int c) {
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].next = head[u];
    edge[cnt].c = c;
    head[u] = cnt;
}
long long int dfs(int u, long long int flow) {
    if (u == t)return flow;
    visit[u] = true;
    for (int i = head[u]; i != 0; i = edge[i].next) {
        int v = edge[i].v;
        long long int c = edge[i].c;
        if (visit[v] || 0 == c)continue;
        long long int result = dfs(v, c < flow ? c : flow);
        if (result > 0) {
            edge[i].c -= result;
            edge[i ^ 1].c += result;
            return result;
        }
    }
    return 0;
}
long long int Ford_Fulkerson() {
    if (s == t)return 0;
    long long int result = 0;
    long long int ans = dfs(s, MAX);
    while (ans > 0) {
        result += ans;
        memset(visit, false, sizeof(visit));
        ans = dfs(s, MAX);
    }
    return result;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v;
        long long int c;
        scanf("%d%d%lld", &u, &v, &c);
        add_edge(u, v, c);
        add_edge(v, u, 0);
    }
    printf("%lld\n", Ford_Fulkerson());
    return 0;
}

Edmond-Karp

Ford-Fulkerson找增广路的时候,用bfs

这里用前向星

读图的时候,需要直接加入反向边,优点是,反向边只需要异或1就可以获得

比如正向是10,反向边就是11

时间复杂度O(VE^2)

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX=0x7fffffff;
//前向星
struct Edge {
    int v, next, c;
}edge[M];
int head[N];//前向星头
bool visit[N];
int pre[N];
int n, s, t, cnt = 1;
/**
 * 前向星添加边
 * @param u 起点
 * @param v 终点
 * @param c 容量
 */
void add_edge(int u, int v, int c) {
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].next = head[u];
    edge[cnt].c = c;
    head[u] = cnt;
}
bool bfs() {
    queue<int> q;
    q.push(s);
    memset(visit, false, sizeof(visit));
    visit[s] = true;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i != 0; i = edge[i].next) {
            int v = edge[i].v;
            int c = edge[i].c;
            if (visit[v] || 0 == c)continue;
            pre[v] = i;//记录前驱对应的边
            if (v == t)return true;
            q.push(v);
            visit[v] = true;
        }
    }
    return false;
}
LL Edmond_Karp() {
    if (s == t)return 0;
    LL result = 0;
    while (bfs()) {
        int x = t;
        int ans = MAX;
        while (x != s) {
            int i = pre[x];
            if (edge[i].c < ans)ans = edge[i].c;
            x = edge[i ^ 1].v;//从反向边找前驱
        }
        x = t;
        //更新
        while (x != s) {
            int i = pre[x];
            edge[i].c -= ans;
            edge[i ^ 1].c += ans;
            x = edge[i ^ 1].v;
        }
        result += ans;
    }
    return result;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        add_edge(u, v, c);
        add_edge(v, u, 0);
    }
    printf("%lld\n", Edmond_Karp());
    return 0;
}

Dinic算法

时间复杂度:上界O(V^2 E),不过据说这个上界很松

简单来说就是

bfs分层,然后dfs去多路增广,增广时,只从层数低到高(这个就好比你知道目标在东边,你只会走正东,东北,东南,其他方向不会让你更靠近目标)

有几个优化:

         1.送到汇点的流量=0时,不再遍历这个点,也叫做炸点优化

         2.当前弧:就是前几个弧其实已经满流了,下次从还没有满流的点开始遍历

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x7fffffff;
struct Edge {
    int v, next, c;
}edge[M];
int head[N];
int cnt = 1;

int now[N];//当前弧
int level[N];//分层
void add_edge(int u, int v, int c) {
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].c = c;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
int s, t, n;
/**
 * 广度优先,分层
 * @return true到达汇点|false没到达汇点
 */
bool bfs() {
    memset(level, 0, sizeof(level));
    queue<int> q;
    q.push(s);
    level[s] = 1;
    now[s] = head[s];
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i != 0; i = edge[i].next) {
            int v = edge[i].v;
            int c = edge[i].c;
            if (level[v] > 0 || 0 == c)continue;
            level[v] = level[u] + 1;
            now[v] = head[v];
            if (v == t)return true;
            q.push(v);
        }
    }
    return false;
}
/**
 * 深度优先,多路增广
 * @param u 当前点
 * @param flow 流量
 * @return 到汇点的流量
 */
int dfs(int u, int flow) {
    if (u == t)return flow;
    int out = 0;//当前点流到汇点的量
    for (int i = now[u]; i != 0 && flow > 0; i = edge[i].next) {//从当前弧开始遍历
        now[u] = i;//更新当前弧
        int v = edge[i].v;
        int c = edge[i].c;
        if (c == 0 || level[v] != level[u] + 1)continue;
        int ans = dfs(v, c < flow ? c : flow);
        if (ans == 0) {
            level[v] = 0;//前面到达不了汇点,所以把level设成0,代表以后都不走这个点
            continue;
        }
        edge[i].c -= ans;
        edge[i ^ 1].c += ans;
        out += ans;
        flow -= ans;//减少当前容量
    }
    return out;
}
LL Dinic() {
    if (s == t)return 0;
    LL result = 0;
    while (bfs()) {
        result += dfs(s, MAX);
    }
    return result;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        add_edge(u, v, c);
        add_edge(v, u, 0);
    }
    printf("%lld\n", Dinic());
    return 0;
}

ISAP

时间复杂度:上界O(V^2 E),不过据说这个上界很松

前面的dinic是多次bfs,这个isap就只用一次bfs

isap先是从汇点反向bfs,标记了每个点到汇点的距离dis(所以这里一定要存反向边,不然就无法遍历了)

然后就一直dfs,只走dis[u]=dis[v]+1,因为这样可以走最短的增广路

如果某一个点u出去的点都走完了,但是还有剩余的流,就将距离min{dis[v]}+1  

 

gap优化,当dis[i]=k的个数为0,说明断层了,因为你只能走dis[u]=dis[v]+1的,也就是连续的点,如果不连续,说明源点和汇点不连通了,就可以退出了,把源点距离改为顶点个数(因为有向无环图距离最多为顶点个数-1)

当前弧:和刚刚的dinic里一样

递归+多路增广

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x7fffffff;
struct Edge {
    int v, next, c;
}edge[M];
int head[N], cnt = 1;

int dis[N];//到汇点距离
int cur[N];//当前弧
int gap[N];//gap[i]表示到汇点距离为i的个数

void add_edge(int u, int v, int c) {
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].c = c;
    edge[cnt].next = head[u];
    head[u] = cnt;
}

int n, s, t;
/**
 * bfs 反向遍历
 */
void bfs() {
    memset(dis, -1, sizeof(dis));
    //    memset(gap,0,sizeof(gap));
    dis[t] = 0;
    gap[0] = 1;
    queue<int> q;
    q.push(t);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        cur[u] = head[u];
        for (int i = head[u]; i != 0; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] != -1)continue;//遍历过了,跳过
            dis[v] = dis[u] + 1;
            ++gap[dis[v]];
            q.push(v);
        }
    }
}
int dfs(int u, int flow) {
    if (u == t)return flow;
    int out = 0;
    for (int i = cur[u]; i != 0; i = edge[i].next) {
        cur[u] = i;
        int v = edge[i].v;
        int c = edge[i].c;
        if (0 == c || dis[u] != dis[v] + 1)continue;
        int ans = dfs(v, c < flow ? c : flow);
        if (ans > 0) {
            out += ans;
            flow -= ans;
            edge[i].c -= ans;
            edge[i ^ 1].c += ans;
        }
        if (0 == flow || dis[s] == n)return out;
    }
    //到这,u出去的所有边都遍历过了,但是还有没用完的流
    --gap[dis[u]];//修改距离
    if (0 == gap[dis[u]]) {//断层了
        dis[s] = n;
        return out;
    }
    ++dis[u];
    ++gap[dis[u]];
    cur[u] = head[u];//重置当前弧
    return out;
}
LL ISAP() {
    if (s == t)return 0;
    bfs();
    LL result = 0;
    while (dis[s] < n) {
        result += dfs(s, MAX);
    }
    return result;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        add_edge(u, v, c);
        add_edge(v, u, 0);
    }
    printf("%lld\n", ISAP());
    return 0;
}

非递归+单路增广

其实非递归也很好写,但是我写的是单路增广,多路增广好像不太好写,然后非递归需要记录一下前驱 

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x7fffffff;
struct Edge {
    int v, next, c;
}edge[M];
int head[N], cnt = 1;

int dis[N];//到汇点距离
int cur[N];//当前弧
int gap[N];//gap[i]表示到汇点距离为i的个数
int pre[N];//前驱

void add_edge(int u, int v, int c) {
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].c = c;
    edge[cnt].next = head[u];
    head[u] = cnt;
}


int n, s, t;
/**
 * bfs 反向遍历
 */
void bfs() {
    memset(dis, -1, sizeof(dis));
    //    memset(gap,0,sizeof(gap));
    dis[t] = 0;
    gap[0] = 1;
    queue<int> q;
    q.push(t);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        cur[u] = head[u];
        for (int i = head[u]; i != 0; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] != -1)continue;//遍历过了,跳过
            dis[v] = dis[u] + 1;
            ++gap[dis[v]];
            q.push(v);
        }
    }

}
/**
 * 找到增广路之后调整这一路的流量
 * @return 这条增广路送出去的流量
 */
int augment() {
    int u = t;
    int result = MAX;
    //计算流量
    while (u != s) {
        int i = pre[u];
        int c = edge[i].c;
        if (c < result)result = c;
        u = edge[i ^ 1].v;
    }
    u = t;
    //调整
    while (u != s) {
        int i = pre[u];
        edge[i].c -= result;
        edge[i ^ 1].c += result;
        u = edge[i ^ 1].v;
    }
    return result;
}

LL ISAP() {
    if (s == t)return 0;
    LL result = 0;
    bfs();
    int u = s;
    while (dis[s] < n) {
        if (u == t) {//找到增广路
            result += augment();
            u = s;//从源点继续找
        }
        bool flag = false;
        //找下一个可以走的点
        for (int i = cur[u]; i != 0; i = edge[i].next) {
            int v = edge[i].v;
            int c = edge[i].c;
            if (0 == c || dis[u] != dis[v] + 1)continue;
            pre[v] = i;//记录前驱
            cur[u] = i;//当前弧
            u = v;//下一个
            flag = true;
            break;
        }
        if (!flag) {//走到这说明没有可以走的点,需要调整到汇点的距离为min{dis[v]}+1
            int min_dis = n;
            for (int i = head[u]; i != 0; i = edge[i].next) {
                int v = edge[i].v;
                if (edge[i].c > 0 && dis[v] < min_dis)min_dis = dis[v];
            }
            --gap[dis[u]];
            if (gap[dis[u]] == 0) {//gap优化
                break;
            }
            dis[u] = min_dis + 1;
            ++gap[dis[u]];
            cur[u] = head[u];
            if (u != s) {//回溯上一个点
                u = edge[pre[u] ^ 1].v;
            }
        }
    }
    return result;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        add_edge(u, v, c);
        add_edge(v, u, 0);
    }
    printf("%lld\n", ISAP());
    return 0;
}

HLPP

又叫最高标号预留推进

时间复杂度O\left(V^{2} \sqrt{E} \right )

 

简单来说就是把节点看做水库,然后将水流屯起来一起送出去

为了防止你推给我,我推给你,推到TLE,我们要有一个高度,类似isap

水往低处流,所以,我们只允许高的往低的流

如果能流的边都流过一遍了,但是还有剩余流量,就抬高这个节点,让他继续流

 

 

所以算法流程

1.bfs,初始化高度,汇点为0,然后反向bfs

2.建立一个优先队列,针对顶点的高度

3.对于起点,单独处理,把他的出边都流一遍,把对应的顶点都加入优先队列

4.从优先队列出一个点,对于高度比他低1的点推流,如果这些点不在队列,就加入队列(算法中的push)

5.如果刚刚那个顶点还有剩余流量,我们就修改高度,改成他出边顶点中最低的顶点的高度+1,然后再入队

6.重复4-5,直到队空

 

gap优化,和isap类似,如果这个高度没有了,我们就把高于这个顶点且小于n+1的顶点,高度设置为n+1,让他尽快把流量流回源点

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x3f3f3f3f;
struct Edge {
    int v, next, c;
} edge[M];
int head[N], cnt = 1;

int h[N];//每个点高度
LL e[N];//每个点的容量
int gap[N * 2];//这个高度完全有可能到2N-1,所以,开2倍
bool inq[N];//在队列里

struct cmp {
    bool operator()(int left, int right)const {
        return h[left] < h[right];
    }
};
priority_queue<int, vector<int>, cmp> pq;

void add_edge(int u, int v, int c) {
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].c = c;
    edge[cnt].next = head[u];
    head[u] = cnt;
}


int n, s, t;
/**
 * 反向遍历
 * @return 能否到达源点
 */
bool bfs() {
    memset(h, 0x3f, sizeof(h));
    //    memset(e,0,sizeof(e));
    //    memset(gap,0,sizeof(gap));
    //    memset(inq,false,sizeof(inq));
    queue<int> q;
    q.push(t);
    h[t] = 0;
    gap[0] = 1;

    while (!q.empty()) {
        int u = q.front();
        q.pop();

        for (int i = head[u]; i != 0; i = edge[i].next) {
            int v = edge[i].v;
            int c = edge[i ^ 1].c;

            if (0 == c || h[v] <= h[u] + 1)
                continue;

            h[v] = h[u] + 1;
            ++gap[h[v]];
            q.push(v);
        }
    }

    return h[s] != MAX;
}
/**
 * 推流
 * @param u 推流点
 */
void push(int u) {
    for (int i = head[u]; i != 0; i = edge[i].next) {
        int v = edge[i].v;
        int c = edge[i].c;

        if (0 == c || h[u] != h[v] + 1)continue;

        int flow = c < e[u] ? c : e[u];
        edge[i].c -= flow;
        edge[i ^ 1].c += flow;
        e[u] -= flow;
        e[v] += flow;

        if (v != s && v != t && !inq[v]) {
            inq[v] = true;
            pq.push(v);
        }

        if (0 == e[u])return;
    }
}
/**
 * 重贴标签,抬高u的高度,使得刚好能流向想一个节点
 * @param u 重贴标签的点
 */
void relabel(int u) {
    h[u] = MAX;

    for (int i = head[u]; i != 0; i = edge[i].next) {
        int v = edge[i].v;

        if (0 == edge[i].c || h[v] + 1 >= h[u])continue;

        h[u] = h[v] + 1;
    }
}
LL HLPP() {
    if (s == t)return 0;
    if (!bfs())return 0;
    h[s] = n;
    //起点单独走
    for (int i = head[s]; i != 0; i = edge[i].next) {
        int v = edge[i].v;
        int c = edge[i].c;
        if (0 == c)continue;
        edge[i].c = 0;
        edge[i ^ 1].c = c;
        e[s] -= c;
        e[v] += c;
        if (v != s && v != t && !inq[v]) {
            pq.push(v);
            inq[v] = true;
        }
    }
    while (!pq.empty()) {
        int u = pq.top();
        pq.pop();
        inq[u] = false;
        push(u);
        if (e[u] != 0) {//还有流量
            --gap[h[u]];
            if (0 == gap[h[u]]) {
                //把高于这个顶点的,高度改成n+1,让他们把流量流回源点
                for (int i = 1; i <= n; ++i) {
                    if (i != s && i != t && h[i] > h[u] && h[i] < n + 1) {
                        h[i] = n + 1;
                    }
                }
            }

            relabel(u);
            ++gap[h[u]];
            pq.push(u);
            inq[u] = true;
        }
    }

    return e[t];
}

int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        add_edge(u, v, c);
        add_edge(v, u, 0);
    }

    printf("%lld\n", HLPP());
    return 0;
}

终极优化

主要针对洛谷P4722,以及loj127

 

优化1:桶排代替优先队列,需要记录一下当前最高的,然后就可以桶排了,就每一个高度开一个桶

优化2:断层后,删除大于等于那个高度的顶点,反正也流不到汇点

优化3:开vector和list这种,开了O2就挺快的,这种时候邻接表找反向边需要额外记录反向边的位置

优化4:原始版本每次推流结束都要更新一次。然而实际上推流过程中就可以进行更新标签。因此只需要刚开始就设置好初始高度,此后在玄学的push里顺便更新就行了

优化5:快读(我没写,你可以写一下)

#include<cstdio>
#include<vector>
#include<list>
const int N = 1205;
const int INF = 0x7fffffff;
struct Edge {
    int v;//邻接点
    int c;//流量
    int nxt;//反向边
    Edge(int v, int c, int nxt) :v(v), c(c), nxt(nxt) {}
};
std::vector<Edge> edge[N];//邻接表
std::vector<int> h;//顶点高度
std::vector<int> gap;//每个高度的个数
std::vector<int> bucket[N];//桶排
std::vector<int> e;//每个顶点的剩余流量
std::list<int> lst[N];//高度为i的顶点
std::vector<std::list<int>::iterator> it;//顶点在list中的位置
int cur_max_height;//当前最高高度
int cur_height;//当前高度
int n, s, t;
/**
 * 添加边
 * @param u 顶点
 * @param v 顶点
 * @param c 流量
 */
void add_edge(int u, int v, int c) {
    edge[u].push_back(Edge(v, c, edge[v].size()));
    edge[v].push_back(Edge(u, 0, edge[u].size() - 1));
}
/**
 * 全局重贴标签
 */
void global_relabel() {
    h.assign(n + 1, n);
    gap.assign(n + 1, 0);
    h[t] = 0;
    std::vector<int> q(n + 1);//队列
    int front = 0, rear = 0;
    q[rear++] = t;
    while (front != rear) {
        int u = q[front++];
        for (std::vector<Edge>::iterator p = edge[u].begin(); p != edge[u].end(); ++p) {
            int v = p->v;
            int c = edge[v][p->nxt].c;
            if (h[v] != n || 0 == c)continue;
            h[v] = h[u] + 1;
            ++gap[h[v]];
            q[rear++] = v;
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (h[i] >= n)continue;
        /*
         * 相当于
         * lst[i].push_front(i);
         * it[i]=lst[i].begin()
         */
        it[i] = lst[h[i]].insert(lst[h[i]].begin(), i);//加入高度为h[i]的点击
        if (e[i] > 0) {//有剩余流量,加入桶排
            bucket[h[i]].push_back(i);
        }
    }
    cur_height = cur_max_height = h[q[rear - 1]];
}
/**
 * 根据边推流
 * @param u 顶点
 * @param ed 推流
 */
void push(int u, Edge& ed) {
    int v = ed.v;
    int flow = std::min(e[u], ed.c);
    ed.c -= flow;
    edge[v][ed.nxt].c += flow;
    e[u] -= flow;
    e[v] += flow;

    if (e[v] > 0 && e[v] <= flow) {//防止起点,终点
        bucket[h[v]].push_back(v);//加入桶排
    }
}
/**
 * 推流
 * @param u 顶点
 */
void push(int u) {
    int relabel_height = n;
    int temp_height = h[u];
    for (std::vector<Edge>::iterator p = edge[u].begin(); p != edge[u].end(); ++p) {
        if (0 == p->c)continue;
        if (h[u] == h[p->v] + 1) {
            push(u, *p);
            if (0 == e[u])return;
        }
        else {
            relabel_height = std::min(h[p->v] + 1, relabel_height);
        }
    }
    if (1 == gap[temp_height]) {//gap优化
        for (int i = temp_height; i <= cur_max_height; ++i) {
            for (std::list<int>::iterator p = lst[i].begin(); p != lst[i].end(); ++p) {
                --gap[h[*p]];
                h[*p] = n;
            }
            lst[i].clear();
            cur_max_height = temp_height - 1;
        }
    }
    else {//重贴标签
        --gap[temp_height];
        lst[temp_height].erase(it[u]);
        h[u] = relabel_height;
        if (relabel_height == n)return;
        //抬高了高度

        ++gap[relabel_height];
        it[u] = lst[relabel_height].insert(lst[relabel_height].begin(), u);
        cur_height = relabel_height;//修改当前高度,因为高度变高了
        cur_max_height = std::max(cur_max_height, cur_height);
        bucket[relabel_height].push_back(u);
    }

}
int HLPP() {
    if (s == t)return 0;
    //    cur_max_height=cur_height=0;
    //    h[s]=n;
    it.resize(n + 1);
    //    for(int i=1;i<=n;++i){
    //        if(i==s)continue;
    //        it[i]=lst[h[i]].insert(lst[h[i]].begin(),i);
    //    }
    //    gap.assign(n+1,0);
    //    gap[0]=n-1;
    e.assign(n + 1, 0);
    //防止超出INF
    e[s] = INF;
    e[t] = -INF;
    for (std::vector<Edge>::iterator p = edge[s].begin(); p != edge[s].end(); ++p) {
        int v = p->v;
        int flow = p->c;
        p->c = 0;
        edge[v][p->nxt].c += flow;
        e[s] -= flow;
        e[v] += flow;
    }
    global_relabel();
    if (h[s] == 0)return 0;
    while (cur_height != 0) {
        if (bucket[cur_height].empty()) {
            --cur_height;
        }
        else {
            int u = bucket[cur_height].back();
            bucket[cur_height].pop_back();
            push(u);
        }
    }
    return e[t] + INF;
}
int main() {
    int m;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        add_edge(u, v, c);
    }
    printf("%d\n", HLPP());
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nightmare004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值