Network flow souvenir

Brief introduction

This article describes some basic knowledge about network flow, is limited to the individual level, there may be fallacious article, I hope readers find correction.

Issues into

For some reason can not be described, some of the water pipes leading from $ Kirino $ $ Ayase $ family home, now known each pipe can withstand the flow of water and asked if $ Kirino $ steady flow of water from the source, this $ Ayase $ maximum flow side will get how much.

concept

First, some necessary concepts.

Source: just flow out of the points.

Meeting Point: Only incoming traffic points.

Capacity: a maximum size of a flow to the bearer side.

Flow rate: a traffic volume has been carried to the edge.

Augmenting paths: one from source to sink on each side of the remaining traffic are greater than $ 0 $ path.

Residual network: at any time, all the network nodes and the configuration of edges in the remaining capacity of greater than $ 0 $.

nature

Capacity limit: not more than one side of the flow capacity.

Flow conservation: In addition to the source and sink, it does not store any point in the stream, which flows into the outflow amount equal to the total amount.

Antisymmetry: the reverse side of each flow is the opposite of the forward edge of the traffic.

For this picture clearly 1-2-3-4 $ $ $ and $ 1-2-4,1-3-4 both flow method, but the latter is superior to the former, in order to build the anti-side is the maximum flow algorithm Another solution to this problem. When we went 1-2-3-4 $ $ After this road, because of the reverse side, we will come out in the next calculation $ $ 1-3-2-4 these two routes overlap with the results obtained That is the optimal solution for this picture. Next, we discuss how to solve the maximum flow into the system using the method.

$ EK $ augmenting path algorithm

The basic idea of ​​$ EK $ is constantly looking for with $ bfs $ augmenting path on the network, the way to get the augmented capacity of the minimum remaining $ minf $, the answer plus $ minf $.

Every time looking only consider capacity greater than the current flow edge, an edge when the $ i $ is considered on its anti-symmetry is clearly anti-edge $ i \ oplus1 $ satisfy this condition, so finding process will consider the reverse side . Since the number of edges in the process of FIG building is started from $ 0 $, $ i so the first side of the corresponding forward reverse sides apparently $ $ i \ oplus1 $. New Canton Road more information needs to be recorded at the same point number with the number precursors edge nodes to facilitate statistical currently augmenting path contribution to the answer. Time complexity $ O (nm ^ {2}) $.

Reference Code:

#include <bits/stdc++.h>
#define DBG(x) cerr << #x << " = " << x << endl

using namespace std;
typedef long long LL;

const int N = 1e5 + 5;
const int M = 2e5 + 5;
const int inf = 0x3f3f3f3f;

int head[N], tot;
bool vis[N];

struct Enode {
    int to, next, flow;
} edge[M];

struct Pnode {
    int pid, eid;
} pre[N];

void addedge(int u, int v, int f) {
    edge[tot].to = v;
    edge[tot].flow = f;
    edge[tot].next = head[u];
    head[u] = tot++;
}

bool bfs(int s, int t) {
    memset(vis, false, sizeof vis);
    queue<int> q;
    q.push(s), vis[s] = 1;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = head[x]; i != -1; i = edge[i].next) {
            int v = edge[i].to;
            if (!vis[v] && edge[i].flow) {
                pre[v].pid = x;
                pre[v].eid = i;
                vis[v] = 1;
                if (v == t) return true;
                q.push(v);
            }
        }
    }
    return false;
}

LL EK(int s, int t) {
    LL res = 0;
    while (bfs(s, t)) {
        LL minv = inf;
        for (int i = t; i != s; i = pre[i].pid) minv = min(minv, 1LL * edge[pre[i].eid].flow);
        for (int i = t; i != s; i = pre[i].pid) {
            edge[pre[i].eid].flow -= (int)minv;
            edge[pre[i].eid ^ 1].flow += (int)minv;
        }
        res += minv;
    }
    return res;
}

int main() {
    memset(head, -1, sizeof head);
    int n, m, s, t;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    for (int i = 1, u, v, w; i <= m; i++) {
        scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, 0);
    }
    printf("%lld\n", EK(s, t));
    return 0;
}

$Dinic$算法

$EK$算法每次只能找出一条增广路,效率还有待提高。我们看看$Dinic$的过程。

$Dinic$不断重复以下步骤,直到不存在$S$到$T$的增广路:

$1$.在残量网络上$bfs$构造分层图。

$2$.在分层图上$dfs$寻找增广路,更新路径信息,答案加上得到的流量。

$Dinic$有两个优化:

$1$.当前弧优化,即每次考虑某个点的出边时只考虑那些还没被增广的,因为如果一条边已经被增广过,那么它就没有可能被增广第二次。

$2$.多路增广,每次找到一条增广路的时候,如果残余流量没有用完怎么办呢?我们可以利用残余部分流量,再找出一条增广路。这样就可以在一次 $dfs$ 中找出多条增广路。

时间复杂度$O(n^{2}m)$在求解二分图最大匹配时可以达到$O(m\sqrt{n})$。

$ps:$由于我自己在写题过程中没有因为没加多路增广优化而被卡掉过,所以给出的代码是单路增广的版本,有需要的同学可以自行探索或生产。

参考代码:

struct Dinic {
    static const int maxn = 1e6+5;
    static const int maxm = 4e6+5;

    struct Edge {
        int u, v, next, flow, cap;
    } edge[maxm];

    int head[maxn], level[maxn], cur[maxn], eg;

    void addedge(int u, int v, int cap) {
        edge[eg]={u,v,head[u],0,cap},head[u]=eg++;
        edge[eg]={v,u,head[v],0,  0},head[v]=eg++;
    }

    void init() {
        eg = 0;
        memset(head, -1, sizeof head);
    }

    bool makeLevel(int s, int t, int n) {
        for(int i = 0; i < n; i++) level[i] = 0, cur[i] = head[i];
        queue<int> q; q.push(s);
        level[s] = 1;
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            for(int i = head[u]; ~i; i = edge[i].next) {
                Edge &e = edge[i];
                if(e.flow < e.cap && level[e.v] == 0) {
                    level[e.v] = level[u] + 1;
                    if(e.v == t) return 1;
                    q.push(e.v);  
                }
            }
        }
        return 0;
    }

    int findpath(int s, int t, int limit = INT_MAX) {
        if(s == t || limit == 0) return limit;
        for(int i = cur[s]; ~i; i = edge[i].next) {
            cur[edge[i].u] = i;
            Edge &e = edge[i], &rev = edge[i^1];
            if(e.flow < e.cap && level[e.v] == level[s] + 1) {
                int flow = findpath(e.v, t, min(limit, e.cap - e.flow));
                if(flow > 0) {
                    e.flow += flow;
                    rev.flow -= flow;
                    return flow;
                }
            }
        }
        return 0;
    }

    int max_flow(int s, int t, int n) {
        int ans = 0;
        while(makeLevel(s, t, n)) {
            int flow;
            while((flow = findpath(s, t)) > 0) ans += flow;
        }
        return ans;
    }
} di;

最小割定理

给定一个网络$G=(V,E)$,源点为$S$,汇点为$T$。若一个边集$E^{'}\subseteq E$被删去后,$S$和$T$不再联通,则称该边集为网络的割。边容量之和的最小的割称为网络的最小割。

一个网络的最小割等于它的最大流。

费用流

问题引入

由于$Kirino$家的水全排到了$Ayase$家,她为了补偿$Ayase$决定给每条水管都附一个价值$c$,当$f$数量的流量流过时$Kirino$会补偿给$Ayase$的钱为$c*f$。问在流量最大的前提下$Kirino$最少要补偿多少钱。

基于$EK$的费用流算法

在最大流的$EK$算法求解最大流的基础上,把用$bfs$求解任意增广路改为用$spfa$求解费用之和最小的增广路即可,相当于把花费$w(x,y)$作为边权,在残存网络上求最短路。需要注意的是,一条反向边$(y, x)$的费用应该设置为$-w(x, y)$。

容易想到,如果问题要求我们考虑的时最大费用,我们只需在$spfa$的过程中维护最大费用,做法是建边时使用负边权,做完最短路处理之后得到的答案取反。

代码参考(poj2135):

int t, pre[maxn], dis[maxn], head[maxn], vis[maxn];

struct node{
    int u, v, c, f, next;
}e[maxn * 40];

void add1(int u, int v, int c, int f){
    e[t].u = u;
    e[t].v = v;
    e[t].c = c;
    e[t].f = f;
    e[t].next = head[u];
    head[u] = t++;
}

void add(int u, int v, int c, int f){
    add1(u, v, c, f);
    add1(v, u, -c, 0);
}

int spfa(int s,int t){
    int i, u, v;
    queue<int> q;
    q.push(s);
    memset(vis, 0, sizeof vis);
    memset(pre, -1, sizeof pre);
    for(int i = s; i <=t ; i++) dis[i] = inf;
    dis[s] = 0;
    while(!q.empty()){
        u = q.front();
        q.pop();
        for(i = head[u]; i != -1; i = e[i].next){
            v = e[i].v;
            if(e[i].f && dis[v] > dis[u] + e[i].c){
                dis[v] = dis[u] + e[i].c;
                pre[v] = i;
                if(!vis[v]){
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
        vis[u] = 0;
    }
    if(dis[t] != inf) return 1;
    return 0;
}

void solve(int s,int t){
    int ans = 0, i, j, flow = 0, cost = 0;
    while(spfa(s, t)) {
        int minf = inf;
        for(i = pre[t]; i != -1; i = pre[e[i].u]){
            if(e[i].f < minf){
                minf = e[i].f;
            }
        }
        flow += minf;
        for(i = pre[t]; i != -1; i = pre[e[i].u]){
            j = i ^ 1;
            e[i].f -= minf;
            e[j].f += minf;
        }
        cost += dis[t] * minf;
    }
    printf("%d\n", cost);
}

void init(){
    t=0;
    memset(head, -1, sizeof head);
}

int main(){
    int i, u, v, c, n, m;
    scanf("%d%d", &n, &m);
	init();
    for(int i = 0; i < m; i++){
        scanf("%d%d%d", &u, &v, &c);
        add(u, v, c, 1);
        add(v, u, c, 1);
    }
    add(0, 1, 0, 2);
    add(n, n + 1, 0, 2);
    solve(0, n + 1);
    return 0;
}

上下界网络流

无源汇上下界可行流

一张网络中没有源点和汇点,每条边的流量都被限制在区间$[L_i,R_i]$中,问该网络中达到可行流量下每条边的流量。

首先每条边必然要满足下界,我们称此时的流量为初始流,显然此时的网络不一定满足流量守恒,为了满足流量守恒,我们建立超级源点$SS$和超级汇点$TT$,为每个可能不满足流量守恒的点提供附加流,显然对于一条流量下限为$L_i$的有向边$(u, v)$,需要为$u$点的附加流减去$L_i$贡献,为$v$加上$L_i$贡献。之后求解最大流,如果最大流满流,即$maxflow=sum$其中$sum$为所有超源所连向的点的附加流,那么这个当前方案是可行的。

有源汇上下界可行流

一张网络中有源点和汇点$S,T$,每条边的流量都被限制在区间$[L_i,R_i]$中,问该网络中达到可行流量下每条边的流量以及$S$到$T$的流量。

将$T$到$S$连一条流量为$inf$的边,此时就转化成了无源汇的情况。

由于流量守恒对于$S$和$T$依然成立且流入$S$的边仅有$(T,S)$一条,所以根据流量守恒可知边$(T,S)$上的流量即原图上的可行流,根据反向边的性质,这个信息正好被记录在了$(T,S)$的反向边上的流量。

有源汇上下界最大流

当条件变成求最大流之后,我们首先考虑求出可行流,如果可行,我们只需要再在残量网络上以初始的源点和汇点$S,T$求一次最大流,答案显然就是可行流的流量加上残量网络上最大流的流量

有源汇上下界最小流

先给做法:

$1$.根据附加流建立加入超源超汇$SS,TT$的图,对$(SS,TT)$做一次最大流。

$2$.原图连一条$(T,S)$流量为$inf$的边,对$(SS,TT)$做一次最大流。

$3$.当附加边都满流时,答案为$(T,S)$反向边上的流量。

粗略证明:不加$(T,S)$时求过一次最大流,此时尽可能使能流的流量流入了不会增大答案的边,此时再连上$(T,S)$后跑最大流,如果附加流满流,那么就可以达到减小最终答案的目的。

最大权闭合子图

闭合图是什么?在一个图中,我们选取一些点构成集合,若集合中任意点连接的任意出弧,所指向的终点也在该点集中,则这个集合以及所有这些边构成闭合图。最大权闭合子图即点权之和最大的闭合图。求一张图的最大权闭合子图的步骤如下:

$1$.建立源点$S$连向正权点,流量为点的权值。建立汇点$T$,令所有负权点连向$T$,流量为点权值的绝对值。点与点之间根据原来的方向顺序连流量为$inf$的边。

$2$.图$G$的最大权闭合子图$G^{'}$的权值和为所有$G$中的正点权和$sum$减去$S$到$T$的最大流$flow$。

 

 

Guess you like

Origin www.cnblogs.com/DuskOB/p/11216861.html