上下界网络流详解


知乎算法学习笔记

上下界网络流详解

一、无源汇上下界可行流

无源汇上下界网络流,如果有可行流,则必定是一副强连通分量。

模型

给定一个$n$个点$m$条边的图,每条边有一个下限流量$L_{i,j}$和一个上限流量$R_{i,j}$,求出是否存在一种方案使得在满足流量平衡的情况下所有边均满足上下界条件。

流量平衡:每个点流入的流量等于该点流出的流量

解决方法

首先每条边的下限肯定是要流满的,我们先让每条边流$L_{i,j}$那么多的流量,然后将其流量设为$R_{i,j}-L_{i,j}$,如图所示:

观察到只流下限时会出现流量不平衡的点,那么就会出现一些问题,如图所示:

这样一张图明显不存在可行流,但是如果我们减去$L_{i,j}$,就会得到:

如果不看约束条件的话,这张图存在着可行流,且最大流为$10$。

这些问题都是由于流量不平衡导致的,那么我们怎么使流量平衡呢?

因为在一般的网络流中,一个点的流入的流量都来自源点,流出的流量都会到汇点,所以我们可以新建超级源点和超级汇点,对于一个点,有如下三种情况:

$1.in_x>out_x:$多流入了流量应该由源点补足,连边$(s,x,in_x-out_x)$

$2.in_x=out_x:$流量平衡,不需要连边(当然如果想的话可以连)

$3.in_x<out_x:$多流出的流量应流入汇点,连边$(x,t,out_x-in_x)$

所以前面的图应变为:

以右上角的点为例,流入它的下界为25,流出的为10,为了使这两条边下界都为0,则两条边同时流过10的流量(流出的边的下界),因此,两条边的上下界都-10。
变成了流入的边[15, 45],流出的边[0,20],然而,流入边的下界依然没有达到0,因此可以加一条从源点到该点流量为15的边,而流入的边,上界即为30,下界为0.

然后跑最大流算法,如果从$s$流出的边均满流,那么代表所有点的流量均平衡,所得到的结果就是原图的一条可行流。

观察可得,上图无可行流。

二、有源汇上下界可行流

在原图的基础上多了源点与汇点,而这两个点肯定流量不平衡,观察到源点的流入一定等于汇点的流出,为了使两个点流量平衡,我们增加边$(t,s)$,下界为0,上界为$inf$,然后就可以使用无源汇上下界可行流的方法了。

[问题]之前考虑,为什么不可以使用本身的源点进行连边,去平衡其他点的流量守恒。因为从S点流出的流量不确定,没有办法去计算新边的流量是否满流。

三、有源汇上下界最大流

先跑出一条可行流

观察到上图的可行流为$95$,我们接下来看残量网络:

观察到超级源点与超级汇点连出的边已经流满(可行流存在的条件),而最大流等于可行流$+$还能向上浮动的流量,相当于尽量跑满上限,

由于原图中的源汇之间还可以继续找增广路,故还能向上浮动的流量就为$S$到$T$的最大流。

四、有源汇上下界最小流

跟上面几乎完全相同,只需要在拆掉附加边后,从汇点到源点,而不是从源点到汇点跑一遍最大流。可行流的流量,减去从汇点到源点的最大流即为答案。如果说上下界最大流是把残量网络“榨干”,那么上下界最小流就是把不需要的流“退回”。

注意:求出可行流后应删去边$(T,S,inf)$

 

无源汇上下界可行流


#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 210, M = (10200 + N) * 2, INF = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], l[M], ne[M], idx;
int q[N], d[N], cur[N], A[N];

void add(int a, int b, int c, int d)
{
    e[idx] = b, f[idx] = d - c, l[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

bool bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T) return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;
}

int find(int u, int limit)
{
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i])
    {
        cur[u] = i;
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}

int main()
{
    scanf("%d%d", &n, &m);
    S = 0, T = n + 1;
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        add(a, b, c, d);
        A[a] -= c, A[b] += c;
    }

    int tot = 0;
    for (int i = 1; i <= n; i ++ )
        if (A[i] > 0) add(S, i, 0, A[i]), tot += A[i];
        else if (A[i] < 0) add(i, T, 0, -A[i]);

    if (dinic() != tot) puts("NO");
    else
    {
        puts("YES");
        for (int i = 0; i < m * 2; i += 2)
            printf("%d\n", f[i ^ 1] + l[i]);
    }
    return 0;
}

作者:yxc

有源汇上下界最大流


#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 210, M = (N + 10000) * 2, INF = 1e8;

int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N], A[N];

void add(int a, int b, int c)
{
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

bool bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T) return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;
}

int find(int u, int limit)
{
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i])
    {
        cur[u] = i;
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}

int main()
{
    int s, t;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    S = 0, T = n + 1;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        add(a, b, d - c);
        A[a] -= c, A[b] += c;
    }

    int tot = 0;
    for (int i = 1; i <= n; i ++ )
        if (A[i] > 0) add(S, i, A[i]), tot += A[i];
        else if (A[i] < 0) add(i, T, -A[i]);

    add(t, s, INF);
    if (dinic() < tot) puts("No Solution");
    else
    {
        int res = f[idx - 1];
        S = s, T = t;
        f[idx - 1] = f[idx - 2] = 0;
        printf("%d\n", res + dinic());
    }

    return 0;
}

作者:yxc

有源汇上下界最小流


#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 50010, M = (N + 125003) * 2, INF = 2147483647;

int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N], A[N];

void add(int a, int b, int c)
{
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

bool bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T) return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;
}

int find(int u, int limit)
{
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i])
    {
        cur[u] = i;
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}

int main()
{
    int s, t;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    S = 0, T = n + 1;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        add(a, b, d - c);
        A[a] -= c, A[b] += c;
    }

    int tot = 0;
    for (int i = 1; i <= n; i ++ )
        if (A[i] > 0) add(S, i, A[i]), tot += A[i];
        else if (A[i] < 0) add(i, T, -A[i]);

    add(t, s, INF);

    if (dinic() < tot) puts("No Solution");
    else
    {
        int res = f[idx - 1];
        S = t, T = s;
        f[idx - 1] = f[idx - 2] = 0;
        printf("%d\n", res - dinic());
    }

    return 0;
}

作者:yxc

网络流之关键边

https://www.acwing.com/problem/content/2238/

#include<iotream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<quque>
#define inf 0x7fffffff
using namespace std;
const int mxm = 10010;
int cnt = 1, res, b[505], S, T, f[mxm];
struct{
    int to,nxt,w;
}edge[mxm];
void add(int u, int v, int w){
    cnt++;
    edge[cnt].to = v;
    edge[cnt].nxt = hd[u];
    edge[cnt].w = w;
    hd[u] = cnt;
}
bool bfs(){
    queue<int> q; memset(d, -1, sizeof d);
    q.push(S);
    while(q.empty()){
        int u = q.front(); q.pop();
        for(int i = hd[u]; ~i; i = edge[i].nxt){
            int v = edge[i].to;
            if(d[v] == -1 && edge[i].w){
                d[v] = d[u] + 1;
                cur[v] = hd[v];
                if(v == T) return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int find(int u, int res){
    if(u == T) return res;
    int flow = 0;
    for(int &i = hd[u]; ~i; i = edge[i].nxt){
        int v = edge[i].to;
        if(d[v] == d[u] + 1 && f[i]){
            int t = find(v, min(edge[i].w, res - flow);
            if(!t) d[v] = -1;
            edge[i].w -= t, edge[i^1].w += t, flow += t;
        }
    }
    return flow;
}
void dinic(){
    int mxflow = 0, flow;
    while(bfs()){
        flow = 0;
        while(flow = dinic(S, inf)) mxflow += flow;
    }
    return mxflow;
}
void dfs(int u, int vis[], int fl){
    vis[u] = 1;
    for(int i = hd[u]; ~i; i = edge[i].nxt){
        int j = i ^ fl, v = edge[i].to;
        if(f[j] && !vis[v])
            dfs(v, vis, fl);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    S = 0, T = n-1;
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a, b, c);
        add(b, a, 0);
    }
    dinic();
    dfs(S, vis_s, 0);
    dfs(T, vis_t, 1);
    int res;
    for(int i = 0; i < 2*m; i += 2){
        if(!f[i] && vis_s[edge[i^1].to] && vis_t[edge[i].to]){//反向边的to就是正向边的起点
            res++;
        }
    }
    printf("%d\n",res);
    return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM