【Coel.学习笔记】上下界网络流问题

上下界网络流包括无源汇上下界可行流、有源汇上下界最大最小流等。

无源汇上下界可行流

给定一个包含 \(n\) 个点 \(m\) 条边的有向图,每条边都有一个流量下界和流量上界。
求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。

解析:这道题的流量限制与正常的网络流不同,存在下界,即 \(C_l(u,v) \leq f(u,v) \leq C_u(u,v)\)
思考一下怎么把下界转换为 0,即普通的流网络限制。不难想到利用不等式的性质,转化为 \(0\leq f(u,v)-C_l(u,v) \leq C_u(u,v) - C_l(u,v)\)。那么,让 \(C_u(u,v)-C_l(u,v)\) 作为新网络的边。但此时流量守恒不一定能够得到满足,需要进行调整。设初始的流入减流出量为 \(A\)

  1. \(A=0\) 则流量平衡,不需要加附加边。
  2. \(A>0\) 时流入量过大,新建一个源点与该点相连,流量为 \(M\);
  3. \(A<0\) 时流出量过大,新建一个汇点与该点相连,流量与 \(-M\)
    如果附加边满流,就意味着这个点可以实现流量平衡。从新源点向新汇点做最大流,则全部满流时存在满足上下界的可行流。
    代码如下:
// Problem: 无源汇上下界可行流
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2190/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int maxn = 3e4 + 10, inf = 1e9;

int n, m, S, T, tot;
int head[maxn], nxt[maxn], to[maxn], c[maxn], l[maxn], cnt;
int d[maxn], cur[maxn], A[maxn];

void add(int u, int v, int dn, int up) {
    nxt[cnt] = head[u], to[cnt] = v, c[cnt] = up - dn, l[cnt] = dn, head[u] = cnt++;
    nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}

bool bfs() {
    queue<int> Q;
    memset(d, -1, sizeof(d));
    Q.push(S), d[S] = 0, cur[S] = head[S];
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        for (int i = head[u]; i != -1; i = nxt[i]) {
            int v = to[i];
            if (d[v] == -1 && c[i]) {
                d[v] = d[u] + 1;
                cur[v] = head[v];
                if (v == T)
                    return true;
                Q.push(v);
            }
        }
    }
    return false;
}

int find(int u, int limit) {
    if (u == T)
        return limit;
    int flow = 0;
    for (int i = cur[u]; i != -1 && flow < limit; i = nxt[i]) {
        cur[u] = i;
        int v = to[i];
        if (d[v] == d[u] + 1 && c[i]) {
            int t = find(v, min(c[i], limit - flow));
            if (!t) d[v] = -1;
            c[i] -= t, c[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int Dinic() {
    int ans = 0, flow;
    while (bfs())
        while ((flow = find(S, inf)))
            ans += flow;
    return ans;
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    S = 0, T = n + 1;
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= m; i++) {
        int u, v, up, dn;
        cin >> u >> v >> dn >> up;
        add(u, v, dn, up);
        A[u] -= dn, A[v] += dn;
    }
    for (int i = 1; i <= n; i++) {
        if (A[i] > 0)
            add(S, i, 0, A[i]), tot += A[i];
        if (A[i] < 0)
            add(i, T, 0, -A[i]);
    }
    if (Dinic() != tot)
        cout << "NO" << '\n';
    else {
        cout << "YES" << '\n';
        for (int i = 0; i < m * 2; i+= 2)
            cout << c[i ^ 1] + l[i] << '\n';
    }
    return 0;
}

有源汇上下界最大流

洛谷传送门
(洛谷中这道题需要简单地建模为网络流问题,然后按照有源汇上下界最大流的方法做)

给定一个包含 \(n\) 个点 \(m\) 条边的有向图,每条边都有一个流量下界和流量上界。
给定源点 \(S\) 和汇点 \(T\),求源点到汇点的最大流。

解析:本题多了两个限制:有源汇,且要求最大流。

对于第一个限制,我们可以给汇点与源点连一条容量正无穷的边,这样所有点都可以实现流量守恒,可以巧妙地转化成无源汇问题。

对于第二个限制,则可以先求出任意一个可行流,不存在可行流自然最大流也不会存在;然后对残余网络做一遍最大流,将其与可行流流量相加便是答案。

代码如下:

// Problem: P5192 Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5192
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstring>

using namespace std;

const int maxn = 1e6 + 10, inf = 1e9;

int n, m, s, t, S, T, tot;
int head[maxn], nxt[maxn], to[maxn], c[maxn], cnt;
int A[maxn], d[maxn], cur[maxn], q[maxn];

void add(int u, int v, int w) {
    nxt[cnt] = head[u], to[cnt] = v, c[cnt] = w, head[u] = cnt++;
    nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}

bool bfs() {
    memset(d, -1, sizeof(d));
    int hh = 0, tt = -1;
    q[++tt] = S, d[S] = 0, cur[S] = head[S];
    while (tt >= hh) {
        int u = q[hh++];
        for (int i = head[u]; ~i; i = nxt[i]) {
            int v = to[i];
            if (d[v] == -1 && c[i]) {
                d[v] = d[u] + 1;
                cur[v] = head[v];
                if (v == T) return true;
                q[++tt] = v;
            }
        }
    }
    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 = nxt[i]) {
        int v = to[i];
        cur[u] = i;
        if (d[v] == d[u] + 1 && c[i]) {
            int t = find(v, min(c[i], limit - flow));
            if (!t) d[v] = -1;
            c[i] -= t, c[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

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

int main(void) {
    while (cin >> n >> m) {
        memset(head, -1, sizeof(head));
        memset(A, 0, sizeof(A));
        s = 0, t = n + m + 1, cnt = tot = 0;
        S = n + m + 2, T = S + 1;
        for (int i = 1, G; i <= m; i++) {
            cin >> G;
            add(i, t, inf - G);
            A[i] -= G, A[t] += G;
        }
        for (int i = m + 1, C, D; i <= m + n; i++) {
            cin >> C >> D;
            add(s, i, D);
            while (C--) {
                int x, L, R;
                cin >> x >> L >> R;
                x++;
                add(i, x, R - L);
                A[i] -= L, A[x] += L;
            }
        }
        for (int i = 0; i <= n + m + 1; 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)
            cout << -1 << '\n' << '\n';
        else {
            int res = c[cnt - 1];
            c[cnt - 1] = c[cnt - 2] = 0;
            S = s, T = t;
            cout << res + dinic() << '\n' << '\n';
        }
    }
    return 0;
}

有源汇上下界最小流

与上一题相似,不过把最大换成了最小。
解析:类似的操作,把可行流流量减去从汇点到源点的最大流即为答案。
直接把 S = s, T = t 换成 S = t, T = s 并注意一下数据范围即可,代码略。

多源汇最大流

有向图存在多个源点与汇点,求最大流。
解析:建立虚拟源点与虚拟汇点,分别与源汇点连上容量为正无穷的边,做最大流即可。
试着证明一下?首先对于虚拟源汇点之外的部分没有变化,因此流量守恒;然后对于那些源汇点而言,由于流入正无穷、流出正无穷,所以也满足流量守恒。因此原图与新图的可行流一一对应,原图最大流即为新图最大流。
代码如下:

// Problem: 多源汇最大流
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2236/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstring>

using namespace std;

const int maxn = 1e6 + 10, inf = 1e8;

int n, m, S, T;
int head[maxn], nxt[maxn], to[maxn], c[maxn], cnt;
int q[maxn], d[maxn], cur[maxn];

void add(int u, int v, int w) {
    nxt[cnt] = head[u], to[cnt] = v, c[cnt] = w, head[u] = cnt++;
    nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}

bool bfs() {
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S, d[S] = 0, cur[S] = head[S];
    while (hh <= tt) {
        int u = q[hh++];
        for (int i = head[u]; ~i; i = nxt[i]) {
            int v = to[i];
            if (d[v] == -1 && c[i]) {
                d[v] = d[u] + 1;
                cur[v] = head[v];
                if (v == T) return true;
                q[++tt] = v;
            }
        }
    }
    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 = nxt[i]) {
        cur[u] = i;
        int v = to[i];
        if (d[v] == d[u] + 1 && c[i]) {
            int t = find(v, min(c[i], limit - flow));
            if (!t) d[v] = -1;
            c[i] -= t, c[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

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

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    memset(head, -1, sizeof(head));
    int sc, tc;
    cin >> n >> m >> sc >>tc;
    S = 0, T = n + 1;
    for (int i = 1; i <= sc; i++) {
        int x;
        cin >> x;
        add(S, x, inf);
    }
    for (int i = 1; i <= tc; i++) {
        int x;
        cin >> x;
        add(x, T, inf);
    }
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        add(u, v, w);
    }
    cout << dinic();
    return 0;
}
posted @ 2022-07-11 17:03  秋泉こあい  阅读(42)  评论(0编辑  收藏  举报