网络流学习笔记

落花浮王杯 提交于 2019-12-06 01:55:10

网络流学习笔记

网络流资料

最大流dinic

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int INF = 1e9;
const int N = 10005;
const int M = 200005;
int to[M], ne[M];
int w[M], h[N], tot = -1;

inline void add(int x,int y,int z) {
    ne[++tot] = h[x], h[x] = tot;
    to[tot] = y, w[tot] = z;
}
template <typename T>
void read(T &x) {
    x = 0; int f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    if (f) x = -x;
}

int n, m, s, t;
int maxflow;

int depth[N], cur[N];
queue<int> q;
bool bfs(void) {
    memset(depth, 0x7f, sizeof(depth));
    while (q.size()) q.pop();
    for (int i = 1;i <= n; i++) cur[i] = h[i];
    depth[s] = 0; q.push(s);
    while (q.size()) {
        int x = q.front(); q.pop();
        for (int i = h[x]; ~i; i = ne[i]) {
            int y = to[i];
            if (depth[y] > INF && w[i]) {
                depth[y] = depth[x] + 1;
                q.push(y);
            }
        }
    }
    return depth[t] <= INF;
}
int dfs(int now, int limit) {
    if (!limit || now == t) return limit;
    
    int flow = 0, f;
    for (int i = cur[now]; ~i; i = ne[i]) {
        int y = to[i]; cur[now] = i;
        if (depth[y] != depth[now] + 1) continue;
        f = dfs(y, min(limit, w[i]));
        if (!f) continue; flow += f; limit -= f;
        w[i] -= f, w[i ^ 1] += f;
        if (!limit) break;
    }
    return flow;
}




int main() {
    read(n), read(m), read(s), read(t);
    memset(h, -1, sizeof(h));
    for (int i = 1;i <= m; i++) {
        int x, y, z; read(x), read(y), read(z);
        add(x, y, z); add(y, x, 0);
    }
    while (bfs()) maxflow += dfs(s, INF);
    printf ("%d\n", maxflow);
    return 0;
}

费用流dinic

注意:

  • 反向边费用为负
  • dfs时用v标记每个点有没有被标记
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N = 50050;
const int M = 500500;
const int INF = 0x3f3f3f3f;

int read(void) {
    int x = 0; bool f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    return f ? -x : x;
}


int h[N], to[M], ne[M];
int cost[M], w[M], tot = 1;

inline void add(int x,int y,int z,int k) {
    ne[++tot] = h[x], h[x] = tot;
    to[tot] = y, w[tot] = z, cost[tot] = k;
}

int n, m, s, t;

int dis[N], cur[N];
int v[N];
queue<int> q;

bool spfa(void) {
    memset(dis, 0x3f, sizeof(dis));
    memset(v, 0, sizeof(v));
    q.push(s); dis[s] = 0;
    while (q.size()) {
        int x = q.front(); q.pop();
        v[x] = 0;
        for (int i = h[x]; i; i = ne[i]) {
            int y = to[i]; if (!w[i]) continue;
            if (cost[i] + dis[x] < dis[y]) {
                dis[y] = cost[i] + dis[x];
                if (!v[y]) {
                    v[y] = 1;
                    q.push(y);
                }
            }
        }
    }
    if (dis[t] >= INF) return 0;
    for (int i = 1;i <= n; i++) cur[i] = h[i];
    return 1;
}

int dfs(int x,int lim) {
    if (lim <= 0 || x == t) return lim;
    int res = 0; v[x] = 1;
    for (int i = cur[x]; i; i = ne[i]) {
        int y = to[i]; cur[x] = i;
        if (v[y] || dis[y] != dis[x] + cost[i]) continue;
        int f = dfs(y, min(lim, w[i]));
        w[i] -= f, w[i^1] += f;
        res += f, lim -= f;
        if (lim <= 0) return res;
    }
    return res;
}

long long ans1, ans2;
int main() {
    freopen("hs.in","r",stdin);
    n = read(), m = read(), s = read(), t = read();
    for (int i = 1;i <= m; i++) {
        int x = read(), y = read(), z = read(), k = read();
        add(x, y, z, k);
        add(y, x, 0, -k);
    }
    while (spfa()) {
        int tmp = dfs(s, INF);
        ans1 += tmp, ans2 += tmp * dis[t];
    }
    cout << ans1 << ' ' << ans2 << endl;
    return 0;
}
        
    

无源汇有上下界可行流

先让每条边流量设为最小值, 发现可能流入流出量并不平衡

但是没问题, 反手一个转化, 要将网络流图加一个附加网络流图, 每条边的流量为最大流量-最小流量

对于每一个点, 都有一个A[i]表示其流入流量与流出流量的差

若A[i] > 0, 说明流多了, 要往外流, 附加流的流入量要小于流出量, 否则就是附加流的流入量要大与流出量

那么我们新建一个超级源点s和超级汇点t

如果A[i]>0, 连一条从s到i的流量为A[i]的边, 否则连一条从i到t的流量为-A[i]的边

跑一遍最大流, 如果最大流等于s的出边流量之和, 那么满足题意, 每条边的流量加上原来的最小流量即为所求

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 400005;

int h[N], ne[N], to[N];
int w[N], tot = 1;

inline void add(int x,int y,int z) {
    ne[++tot] = h[x], h[x] = tot;
    to[tot] = y, w[tot] = z;
}

int read(void) {
    int x = 0; bool f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    if (f) return -x;
    return x;
}

int n, m;
int a[N], ans[N];
int num[N];
int sum, s, t;

const int INF = 0x7fffffff;
int cur[405], dep[405];

queue<int> q;
bool bfs(void) {
    memset(dep, 0, sizeof(dep));
    q.push(s); dep[s] = 1;
    while (q.size()) {
        int x = q.front(); q.pop();
        for (int i = h[x]; i; i = ne[i]) {
            int y = to[i]; if (!w[i] || dep[y]) continue;
            dep[y] = dep[x] + 1;
            q.push(y);
        }
    }
    if (!dep[t]) return false;
    for (int i = 1;i <= n+4; i++) cur[i] = h[i];
    return true;
}

int flow = 0;
int dfs(int x,int lim) {
    if (x == t) return lim;
    int res = 0;
    for (int i = cur[x]; i; i = ne[i]) {
        int y = to[i]; cur[x] = i;
        if (!w[i] || dep[x] + 1 != dep[y]) continue;
        int f = dfs(y, min(lim, w[i]));
        w[i] -= f, w[i^1] += f;
        res += f, lim -= f;
        if (!lim) return res;
    }
    return res;
}
        

int main() {
    n = read(), m = read();
    for (int i = 1;i <= m; i++) {
        int x = read(), y = read(), l = read(), r = read();
        ans[i] = l; a[y] += l, a[x] -= l;
        add(x, y, r - l); add(y, x, 0);
        num[i] = tot;
    }
    s = n + 1, t = n + 2;
    for (int i = 1;i <= n; i++) {
        if (a[i] > 0) {
            sum += a[i];
            add(s, i, a[i]);
            add(i, s, 0);
        }
        else {
            add(i, t, -a[i]);
            add(t, i, 0);
        }
    }
    
    while (bfs()) flow += dfs(s, INF);
    if (sum != flow) {
        cout << "NO\n";
        return 0;
    }
    cout << "YES\n";
    for (int i = 1;i <= m; i++) printf ("%d\n", ans[i] + w[num[i]]);
    return 0;
}

有源汇有上下界可行流 (最大流/最小流)

有汇源有上下界的可行流

设S, T为超级源点和超级汇点, s, t 为源点和汇点

埋伏他一手, 首先把有汇源问题转化为无汇源问题, 闷声发大财

因为源点和汇点入流和出流不平衡, 从t向s连一条无穷大的边使它平衡, 然后在跑上一问题

设tmp = t 到 s的边的流量, res1 = 拆掉无穷大的边从s到t所跑的最大流, res2 = 拆掉无穷大的边从t到s所跑的最大流

最大流, ans = tmp + res1

思路: 先找到一个可行流, 在残余网络中在跑一遍最大流, 使不能产生新流

最小流, ans = tmp - res2

思路: 先找到一个可行流, 从t到s取消流量, 即回退可行流中可以退的流.

最大流

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 400005;

int h[N], ne[N], to[N];
int w[N], tot = 1;

inline void add(int x,int y,int z) {
    ne[++tot] = h[x], h[x] = tot;
    to[tot] = y, w[tot] = z;
}

int read(void) {
    int x = 0; bool f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    if (f) return -x;
    return x;
}

int n, m;
int a[N];
int num[N];
int sum, ss, tt;

const int INF = 0x7fffffff;
int cur[405], dep[405];

queue<int> q;
bool bfs(int s,int t) {
    memset(dep, 0, sizeof(dep));
    q.push(s); dep[s] = 1;
    while (q.size()) {
        int x = q.front(); q.pop();
        for (int i = h[x]; i; i = ne[i]) {
            int y = to[i]; if (!w[i] || dep[y]) continue;
            dep[y] = dep[x] + 1;
            q.push(y);
        }
    }
    if (!dep[t]) return false;
    for (int i = 1;i <= n+4; i++) cur[i] = h[i];
    return true;
}

int flow = 0;
int dfs(int x,int lim,int t) {
    if (x == t) return lim;
    int res = 0;
    for (int i = cur[x]; i; i = ne[i]) {
        int y = to[i]; cur[x] = i;
        if (!w[i] || dep[x] + 1 != dep[y]) continue;
        int f = dfs(y, min(lim, w[i]), t);
        w[i] -= f, w[i^1] += f;
        res += f, lim -= f;
        if (!lim) return res;
    }
    return res;
}
        
int ans, s, t;

int main() {
    n = read(), m = read(), ss = read(), tt = read();
    for (int i = 1;i <= m; i++) {
        int x = read(), y = read(), l = read(), r = read();
        a[y] += l, a[x] -= l;
        add(x, y, r - l); add(y, x, 0);
        num[i] = tot;
    }
    s = n + 1, t = n + 2;
    for (int i = 1;i <= n; i++) {
        if (a[i] > 0) {
            sum += a[i];
            add(s, i, a[i]);
            add(i, s, 0);
        }
        else {
            add(i, t, -a[i]);
            add(t, i, 0);
        }
    }
    add(tt, ss, INF);
    while (bfs(s, t)) flow += dfs(s, INF, t);
    if (sum != flow) {
        cout << "please go home to sleep\n";
        return 0;
    }
    flow = w[tot^1];
    w[tot] = w[tot^1] = 0;
    while (bfs(ss, tt)) {
        dep[n+1] = dep[n+2] = INF;
        flow += dfs(ss, INF, tt);
    }   
    cout << flow << endl;
    return 0;
}

最小流

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
const int N = 800005;

int h[N], ne[N], to[N];
ll w[N], tot = 1;

inline void add(int x,int y,ll z) {
    ne[++tot] = h[x], h[x] = tot;
    to[tot] = y, w[tot] = z;
}

ll read(void) {
    ll x = 0; bool f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    if (f) return -x;
    return x;
}

int n, m;
ll a[N];
ll num[N];
ll sum, ss, tt;

const ll INF = 0x7fffffffffff;
ll cur[N], dep[N];

queue<int> q;
bool bfs(int s,int t) {
    memset(dep, 0, sizeof(dep));
    q.push(s); dep[s] = 1;
    while (q.size()) {
        int x = q.front(); q.pop();
        for (int i = h[x]; i; i = ne[i]) {
            int y = to[i]; if (!w[i] || dep[y]) continue;
            dep[y] = dep[x] + 1;
            q.push(y);
        }
    }
    if (!dep[t]) return false;
    for (int i = 1;i <= n+4; i++) cur[i] = h[i];
    return true;
}

ll flow = 0;
int dfs(int x,ll lim,int t) {
    if (x == t) return lim;
    ll res = 0;
    for (int i = cur[x]; i; i = ne[i]) {
        int y = to[i]; cur[x] = i;
        if (!w[i] || dep[x] + 1 != dep[y]) continue;
        int f = dfs(y, min(lim, w[i]), t);
        w[i] -= f, w[i^1] += f;
        res += f, lim -= f;
        if (!lim) return res;
    }
    return res;
}
        
ll ans, s, t;

int main() {
    n = read(), m = read(), ss = read(), tt = read();
    for (int i = 1;i <= m; i++) {
        ll x = read(), y = read(), l = read(), r = read();
        a[y] += l, a[x] -= l;
        add(x, y, r - l); add(y, x, 0);
        num[i] = tot;
    }
    s = n + 1, t = n + 2;
    for (int i = 1;i <= n; i++) {
        if (a[i] > 0) {
            sum += a[i];
            add(s, i, a[i]);
            add(i, s, 0);
        }
        else {
            add(i, t, -a[i]);
            add(t, i, 0);
        }
    }
    add(tt, ss, INF);
    while (bfs(s, t)) flow += dfs(s, INF, t);
    if (sum != flow) {
        cout << "please go home to sleep\n";
        return 0;
    }
    flow = w[tot^1];
    w[tot] = w[tot^1] = 0;
    while (bfs(tt, ss)) {
        dep[n+1] = dep[n+2] = INF;
        flow -= dfs(tt, INF, ss);
    }   
    cout << flow << endl;
    return 0;
}

最大权闭合子图

一个有向图的闭合图是该有向图的一个点集, 且点集所有的出边所指向的点还在该点集

给每个点分配一个权值, 一个闭合图中点权和最大的叫做最大权闭合子图

由定义可知, 闭合图中可能包含不只一个连通块

可以用网络流来解决, 首先建图

i的点权为w[i]

超级源点s向所有点权为正的点连一条边权为w[i]的边, 超级汇点t向所有点权为负的点连一条边权为-w[i]的边

对于原来有向图上的每条边(u, v), 在网络图上连一条容量为INF的边(u, v), 答案是正点权和减去最小割

最大闭合子图就是与超级源点在同一连通块的点集

证明(proof)

首先, 最小割割断的只能是与源点和汇点相连的边, 因为割断无穷大显然是不优的

性质: 闭合图和简单割互相对应

以下均为绝对值:

S连接的割边分割后在T集边权为正的点的权值和S1
S连接的割边分割后在T集边权为负的点的权值和S2
T连接的割边分割后在S集边权为正的点的权值和T1
T连接的割边分割后在S集边权为负的点的权值和T2

所以最小割的权值为S1 + T2, 最大闭合子图权值为正权减去负权= T1 - T2

相加即为T1 + S1 = 正点权之和

得证

例题

最大密度子图

定义: 一个无向图(V, E) 的密度g = \(\frac{|E|}{|V|}\)

最大密度子图即最大化无向图的密度

考虑分数规划, 二分g的值

设计函数\(H(x) = max \{\sum_{a\in E}1 - \sum_{b \in V}x\}\) 若H(g) > 0 说明密度大于g, else 密度小于等于g

可以证明二分的范围是 \(\frac{|E|}{|V|}\) 到 m, 精度是\(\frac{1}{n^2}\)

反手一步转化, 每条边(u, v)的存在条件是u和v已经存在, 所以考虑最大闭合子图, 8将边化为权值为1的点, 分别向u, v连一条边权为INF的边, 点权为-g, 用上一方法即可

复杂度 \(\Theta(log_nmaxflow(n+m,n+m))\)

此算法还可以继续改进为\(\Theta(log_nmaxflow(n,n+m))\), 详见文头资料

拓展: 带点权和边权也可以哦

二分图的最小点权覆盖集和最大点权独立集

覆盖集: 所有边至少一个端点在覆盖集中

独立集: 一坨点两两之间没有连边

对于边(u, v) 连一条INF边, s向左部点连边权为点权的边, 右部点向t连边权为点权的边

对于一条简单路径s -> u -> v -> t, 至少要割断一条边

u - > v是不可能被割断的, 被割断的一定是s -> u, v -> t, 代表u, v中最多选一个, 删其边代表不选他, 那么最大点权独立集为点权和-最小割, 最小割对应最小点权覆盖集

网络流24题中的思想与解题方案

  • 拆点思想: 一个物品/时间拆成两个点, 两点之间连边表示此物品的使用次数, 费用

  • 分层图思想: 按时间(或其他)分层, 每次新建一个单位时间的图, 在残余网络上跑dinic

  • 转化思想: 将问题转化为模型(最小点权覆盖集, 最大点权独立集, 最大权闭合子图)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!