最大流

最大流

网络流问题

给定指定的一个有向图,其中有两个特殊的点源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\n", 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\n", inf*n-Dinic());
    return 0;
}

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

转载于:https://www.cnblogs.com/MyNameIsPc/p/8964449.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值