网络流 学习笔记

网络流基础知识

一、最大流

网络流主要用来解决带权有向图,从源点到汇点能汇聚的最大权值。

最简单的网络流算法(naive,得到的不一定是最大流):

1.找到一条从源点到终点的简单路径,将这条路径上的最小值视为该条路径的流量,更新原图。

2.不断找从源点到终点的简单路径,进行更新,直到再也找不到,最终可以得到汇点的权值。

Ford–Fulkerson 增广

在naive算法中,每次找到一条简单路径更新后,同时沿着反向增加一条反悔路径,该反悔路径和原来的路径一样可以用来运输流量。

然后我们不停的找从源点到终点的简单路径,直到找不到为止。

可以证明该算法一定可以找到最大流,但是时间复杂度较高O(maxflow*edge)。

Edmonds–Karp 算法

在Ford–Fulkerson 增广的基础上,对时间复杂度进行优化。

每次找简单路径时把原图看成无权图,找最短路

O(m^2*n)

Dinic 算法

BFS找阻塞流,每次更新

O(m*n^2)

二、最小割 Min-cut(边)

实则和最大流为同一问题。

两种理解:1)最小割=最大流 2)在余量图中找S能到达的所有点为S集,其余点为T集,在原图中将S集与T集做切割,所有S到T的边权和为最小割。

最小割点就相当于把点分成两个点连一条边,边被割了就相当于点被割了。

三、费用流

在最大流问题的基础上,给每一条边e一个单位费用ae,在最大化流量的前提下最小化费用\sum a_{e}f_{e}

        解决:对于 EK 算法,把每次寻找边数最少的路径改成每次寻找费用和最小的路径即可。

网络流24题

P4015 运输问题

最小费用最大流问题

把所有货物都连到同一个源上,连到第i个仓库的边的容量为Ai​,费用为0。
每一家零售店又都连到一个汇上,从第i家零售店连出的边的容量为Bi​ ,费用为0。
中间从仓库到零售店的边就按照题目里的说的那样连,容量为+∞,单位费用为 Ci,j

以样例为例:

题目要求同时求出最小费用最大流和最大费用最小流,最大费用实际上就是将边权置为负值,跑最小费用最大流即可。

#include<bits/stdc++.h>

using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 4050;
const int maxn = 1050;
typedef pair<int, int> P;

struct MCMF {
    struct E {
        int from, to, cap, v;

        E() {}

        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };

    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];

    void init(int _n, int _s, int _t) {
        n = _n;
        s = _s;
        t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }

    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }

    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = 1e9;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = 1e9, inq[s] = true;
        queue<int> Q;
        Q.push(s);
        while (!Q.empty()) {
            int u = Q.front();
            Q.pop();
            inq[u] = false;
            for (int &idx: G[u]) {
                E &e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }

    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }


} f;

int a[MAXN], b[MAXN];
int c[MAXN][MAXN];

void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++)cin >> b[i];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> c[i][j];
        }
    }
    f.s = 0;
    f.t = n + m + 1;
    f.init(n + m + 1, 0, n + m + 1);


    for (int i = 1; i <= n; i++) {
        f.add(0, i, a[i], 0);
    }
    for (int i = 1; i <= m; i++) {
        f.add(n + i, n + m + 1, b[i], 0);
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            f.add(i, j + n, INF, c[i][j]);

        }
    }

    cout << f.solve() << '\n';

    f.s = 0;
    f.t = n + m + 1;
    f.init(n + m + 1, 0, n + m + 1);

    for (int i = 1; i <= n; i++) {
        f.add(0, i, a[i], 0);
    }
    for (int i = 1; i <= m; i++) {
        f.add(n + i, n + m + 1, b[i], 0);
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            f.add(i, j + n, INF, -c[i][j]);

        }
    }
    cout << -f.solve() << '\n';
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _ = 1;
    while (_--) solve();
    return 0;
}

P1344 [USACO4.4] 追查坏牛奶 Pollutant Control

最小割板子题

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>

#define int long long
const int N = 1e4 + 5, M = 2e5 + 5;
const int INF = 0x3f3f3f3f3f3f3f;

int n, m, s, t, tot = 1, lnk[N], ter[M], nxt[M], val[M], dep[N], cur[N];
int vis[N];

void add(int u, int v, int w) {
    ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, val[tot] = w;
}

void addedge(int u, int v, int w) { add(u, v, w), add(v, u, 0); }

int bfs(int s, int t) {
    memset(dep, 0, sizeof(dep));
    memcpy(cur, lnk, sizeof(lnk));
    std::queue<int> q;
    q.push(s), dep[s] = 1;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = lnk[u]; i; i = nxt[i]) {
            int v = ter[i];
            if (val[i] && !dep[v]) q.push(v), dep[v] = dep[u] + 1;
        }
    }
    return dep[t];
}

int dfs(int u, int t, int flow) {
    if (u == t) return flow;
    int ans = 0;
    for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
        int v = ter[i];
        if (val[i] && dep[v] == dep[u] + 1) {
            int x = dfs(v, t, std::min(val[i], flow - ans));
            if (x) val[i] -= x, val[i ^ 1] += x, ans += x;
        }
    }
    if (ans < flow) dep[u] = -1;
    return ans;
}

int cnt = 0;

int dinic(int s, int t) {
    int ans = 0;
    while (bfs(s, t)) {
        int x;
        cnt++;
        while ((x = dfs(s, t, 1 << 30))) {
            ans += x;
        }
    }
    return ans;
}

int dfs(int u) {
    vis[u] = 1;
    int ans = 0;
    for (int i = lnk[u]; i; i = nxt[i]) {
        int v = ter[i];
        if (!vis[v] && val[i]) {
            ans += dfs(v);
        } else if (!vis[v])ans++;
    }
    return ans;
}

signed main() {
    scanf("%lld%lld", &n, &m);
    s = 1, t = n;
    while (m--) {
        int u, v, w;
        scanf("%lld%lld%lld", &u, &v, &w);
        addedge(u,v,w*N+1);
    }
    int ANS = dinic(s, t);
    printf("%lld %lld", ANS / N, ANS % N);
    return 0;
}

P2756 飞行员配对方案问题

struct edge {
    int u, v, cap, flow;

    edge() {}

    edge(int u, int v, int cap, int flow) : u(u), v(v), cap(cap), flow(flow) {}
} eg[maxm << 1];

int tot, dis[maxn << 1], cur[maxn << 1];
vector<int> tab[maxn << 1];


void addedge(int u, int v, int cap) {
    tab[u].push_back(tot);
    eg[tot++] = edge(u, v, cap, 0);
    tab[v].push_back(tot);
    eg[tot++] = edge(v, u, 0, 0);
}

int bfs(int s, int t) {
    queue<int> q;
    q.push(s);
    memset(dis, INF, sizeof dis);
    dis[s] = 0;
    while (!q.empty()) {
        int h = q.front(), i;
        q.pop();
        for (i = 0; i < tab[h].size(); i++) {
            edge &e = eg[tab[h][i]];
            if (e.cap > e.flow && dis[e.v] == INF) {
                dis[e.v] = dis[h] + 1;
                q.push(e.v);
            }
        }
    }
    return dis[t] < INF;
}

int dfs(int x, int maxflow, int s, int t) {
    if (x == t || maxflow == 0)
        return maxflow;
    int flow = 0, i, f;
    for (i = cur[x]; i < tab[x].size(); i++) {
        cur[x] = i;
        edge &e = eg[tab[x][i]];
        if (dis[e.v] == dis[x] + 1 && (f = dfs(e.v, min(maxflow, e.cap - e.flow), s, t)) > 0) {
            e.flow += f;
            eg[tab[x][i] ^ 1].flow -= f;
            flow += f;
            maxflow -= f;
            if (maxflow == 0)
                break;
        }
    }
    return flow;
}

int dinic(int s, int t) {
    int flow = 0;
    while (bfs(s, t)) {
        memset(cur, 0, sizeof(cur));
        flow += dfs(s, INF, s, t);
    }
    return flow;
}

int main() {
    scanf("%d%d", &n, &m);
    int u, v;
    while (scanf("%d%d", &u, &v)) {
        if (u == -1 && v == -1)break;
        addedge(u, v, 1);
    }
    for (int i = 1; i <= n; i++) {
        addedge(0, i, 1);
    }
    for (int i = n + 1; i <= m; i++) {
        addedge(i, m + 1, 1);
    }
    int flow = dinic(0, m + 1);

    printf("%d\n", flow);
    for (int i = 1; i <= tot; i += 2) {
        if (eg[i].u <= m && eg[i].u > n && eg[i].v <= n && eg[i].flow == -1)
            printf("%d %d\n", eg[i].v, eg[i].u);
    }
    return 0;
}

P2763 试题库问题

按照题意建图即可。

P4016 负载平衡问题

最小费用最大流,中间相邻点建INF容量,单位费用为1的边。

P2765 魔术球问题

假设有 n 根柱子,现要按下述规则在这 n 根柱子中依次放入编号为 1,2,3,...的球“

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 2 个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4根柱子上最多可放 11 个球。

对于给定的 n,计算在 n根柱子上最多能放多少个球。

最大边覆盖=点数-最大匹配,跑dinic的最大流即可求出最大匹配。

同时将球拆点,与源点汇点相连,同时与它可以匹配的球相连跑最大流即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值