zoukankan      html  css  js  c++  java
  • 最大流

    最大流

    网络流问题

    给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow)。

    显然有流网络有三个性质:1.容量限制:流量不会大于容量。2.反对称性:从u到v的流量一定是从v到u流量的相反值。3.流守恒性:流入与流出相同。这里要特意注意第二条,一条边上的流量v会带来反向的流量c-v,具体体现在代码,就是在残留网络中每条边都有反边,当一条边的流量增加,它的反边的流量就减少相应值。网络流的算法就是基于残留网络而不是基于原网络的。

    残留网络,增广路和割

    增广路就是一条通过残留网络的流,其作用是“反悔”。割C[A, B]是将图G分为A和B两个点集,使得S在A中,T在B中。注意所谓“割中的边”,指的是从属于S的点出发,到T的所有边。

    如何求出一个网络的最大流呢?有一个最简单的EK算法,思路是每次找出一条增广路,不断调整残留网络,直到没有增广路为止。这个证明可以感性愉悦一下。下面是理性不愉悦的证明

    对于一个网络流图G=(V,E),其中有源点s和汇点t,那么下面三个条件是等价的:

    1. 流f是图G的最大流
    2. 残留网络Gf不存在增广路
    3. 对于G的某一个割(S,T),此时f = C(S,T)

    首先证明1 => 2:

    我们利用反证法,假设流f是图G的最大流,但是残留网络中还存在有增广路p,其流量为fp。则我们有流f'=f+fp>f。这与f是最大流产生矛盾。

    接着证明2 => 3:

    假设残留网络Gf不存在增广路,所以在残留网络Gf中不存在路径从s到达t。我们定义S集合为:当前残留网络中s能够到达的点。同时定义T=V-S。

    此时(S,T)构成一个割(S,T)。且对于任意的u∈S,v∈T,有f(u,v)=c(u,v)。若f(u,v)<c(u,v),则有Gf(u,v)>0,s可以到达v,与v属于T矛盾。

    因此有f(S,T)=Σf(u,v)=Σc(u,v)=C(S,T)。

    最后证明3 => 1:

    由于f的上界为最小割,当f到达割的容量时,显然就已经到达最大值,因此f为最大流。

    这样就说明了为什么找不到增广路时,所求得的一定是最大流,大概思想就是利用割作为跳板来证明。

    Dinic

    我们来算一下EK的复杂度。首先我们定义层次图。将残留网络从s开始bfs,计算出每个点是第几层的,层次图中只包含从层次低连到高的边。将t的层数定义为层次图的层数。相同层数的层次图称为一个阶段。

    设原残留网络的层数为k。容易发现增广一次,k要么不变,要么增加。k增加的原因是增广是在层次图上增广的,不会使层次图中的边流量增加,只会增加后向边的流量,因此bfs会走更多的回头路,层数随之增加。(此处感性愉悦,具体见论文

    因此随着增广,层次图的层数严格递增。由于最短路的长度最少为1,最多为n-1,所以最多有n-1个阶段,近似于n。在同一阶段中,每增广一次,就会至少减少一条层次图中的边。所以最多增广m次。而EK算法找增广路用的bfs,所以时间复杂度为O(n*m^2)。

    Dinic唯一做的就是把bfs改成dfs。在每一个阶段中,先用一个bfs将所有的点的层次都确定出来,然后强制dfs只能从层次浅的跑向深的点。这样就可以用dfs代替bfs了。时间复杂度被优化到O(n^2*m),考场上一般写这个。

    注意我加了一个当前弧优化,就是dfs时不用跑已经跑过的弧了,因为边的流量只会少不会多,并且层次图是个dag。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int maxf=55, maxn=maxf*200, maxm=maxn+100, INF=1e9, inf=1e8;
    int n, m, cnt, src, dst, ans;
    inline int min(int x, int y){ return x<y?x:y; }
    struct Edge{
        int to, nxt, f;
    }e[maxm*2];
    int fir[maxn], cnte=1;
    void addedge(int x, int y, int v){
        Edge &ed=e[++cnte];
        printf("%d %d
    ", x, y);
        ed.to=y; ed.nxt=fir[x]; ed.f=v; fir[x]=cnte;
    }
    int q[maxn], head, tail, dep[maxn];
    bool bfs(){
        memset(dep, 0, sizeof(dep)); dep[src]=1;
        head=tail=0; q[tail++]=src; int u;
        while (head<tail){
            u=q[head++];
            for (int i=fir[u]; ~i; i=e[i].nxt)
                if (e[i].f&&!dep[e[i].to]){
                    dep[e[i].to]=dep[u]+1;
                    q[tail++]=e[i].to;
                }
        }
        return dep[dst];
    }
    int cur[maxn];
    int dfs(int u, int flow){
        if (u==dst) return flow;
        for (int i=cur[u]; ~i; i=e[i].nxt, cur[u]=i)
        if (dep[e[i].to]==dep[u]+1&&e[i].f){
            int minm=dfs(e[i].to, min(flow, e[i].f));
            e[i].f-=minm; e[i^1].f+=minm;
            if (minm) return minm;
        }
        return 0;
    }
    int Dinic(){
        int ans=0, t;
        while (bfs()){
            memcpy(cur, fir, sizeof(fir));
            while (t=dfs(src, INF)) ans+=t;
        }
        return ans;
    }
    
    int a[maxf], b[maxf], c[maxf], l[maxf], r[maxf];
    int beg[maxn];  //代表第i个函数的起始点编号
    
    int main(){
        memset(fir, -1, sizeof(fir)); int u, v, d;
        scanf("%d%d", &n, &m); src=++cnt; dst=++cnt; int val;
        for (int i=0; i<n; ++i) scanf("%d%d%d", &a[i], &b[i], &c[i]);
        for (int i=0; i<n; ++i){
            scanf("%d%d", &l[i], &r[i]);
            addedge(0, ++cnt, 2*inf); beg[i]=cnt;
            for (int j=l[i]; j<=r[i]; ++j){
                val=a[i]*j*j+b[i]*j+c[i];
                addedge(cnt, cnt+1, inf-val); ++cnt;
            }
            addedge(cnt, dst, 2*inf);
        }
        while (m--){
            scanf("%d%d%d", &u, &v, &d); --u; --v;
            for (int i=l[v]; i<=r[v]; ++i){
                int j=i+d;
                if (j>r[u]) continue;
                //beg[u]+j-l[u]表示x[u]=x[v]+d的点的编号
                addedge(max(beg[u], beg[u]+j-l[u]), beg[v]+i-l[v], 2*inf);
            }
        }
        printf("%d
    ", inf*n-Dinic());
        return 0;
    }
    

    至于sap。。反正我是不准备学了(不会用到.jpg)

  • 相关阅读:
    leetcode 5286 网格中的最短路径
    C++ STL
    Transformer解读
    global_steps
    tensorflow中的一些语法问题
    生成器与yield关键字
    CNN神经网络一维卷积和二维卷积
    electron-builder打包后,运行的程序左上角图标不显示,桌面和状态栏的显示正常,是什么情况。
    git慢设置代理
    js使用正则表达式获取字符串中特定的字符
  • 原文地址:https://www.cnblogs.com/MyNameIsPc/p/8964449.html
Copyright © 2011-2022 走看看