上下界网络流学习小记

可行流

首先添加超级源ss和超级汇tt
对于u到v一条下限为l上限为r的边,拆成三条边:ss到v连容量为l,u到tt连容量为l,u到v连容量为r-l。
如果原图是有源汇的,则t到s需要连一条下限为0上限为正无穷的边。
做ss到tt的最大流,如果ss的出边都满流,则原图存在可行流。
那如何做s到t最大流和最小流呢?有一种好理解的是,如果做最大流,那么你二分答案x,然后让t到s的边上限为x,然后判断是否有可行流,最小流类似。

最小费用可行流

如果我们要做最小费用可行流,则u到v的边和ss到v的边加上费用c即可,然后直接跑ss到tt的最小费用最大流。
例题:供电网络,支线剧情

最大流和最小流

最大流怎么做,上面已经提到了一种二分。还有一种做法是:
对于新图求ss到tt的最大流tmp1,然后删除ss和tt以及t到s的边,然后求s到t的最大流tmp2,最大流即为tmp1+tmp2.
感性理解一下,第一次最大流要去满足下界,第二次最大流是尽可能再去多流,所以加起来就是最大流。
最小流怎么做,上面已经提到了一种二分。还有一种做法是:
不连t到s的边,对新图做ss到tt的最大流,然后添加t到s的边,对新图做ss到tt的最大流,这时t到s的边的流量即为最小流。
感性理解一下,以下内容转载自_DMute的上下界网络流总结
首先明确,我们的方法是通过加边转化成对任一点都有流量平衡的无源汇的网络,进行求解.
即最终解只能是加上边后,求的无源汇可行流,即T->S这边上的流量. 不改成无源汇的直接求的解是未必正确的,在(1)中已经提到.
然后,因为第一遍做的时候并无这条边,所以S->T的流量在第一遍做的时候都已经尽力往其他边流了. 于是加上T->S这条边后,都是些剩余的流不到其他边的流量. 从而达到尽可能减少T->S这边上的流量的效果,即减小了最终答案.
感觉上第一遍做的既然是不改成无源汇直接求的,应该是错误的?
这里不是错误的. 首先我们的解都是按照第二遍所求的而定,其次这里这样做本质是延迟对T->S这条边的增流.

最小流例题:随便找一道什么最小路径覆盖的题目建个上下界网络流的图就好啦。
这里的代码是使命的召唤

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200*2+10,maxm=maxn*maxn*5+10,inf=1000000000;
int h[maxn],now[maxn],d[maxn],go[maxm],dis[maxm],fx[maxm],next[maxm],x[maxn];
bool bz[maxn];
int i,j,k,l,r,s,t,ss,tt,n,m,tot,ans;
void add(int x,int y,int z,int d){
    go[++tot]=y;
    dis[tot]=z;
    fx[tot]=tot+d;
    next[tot]=h[x];
    h[x]=tot;
}
void link(int x,int y,int l,int r){
    add(ss,y,l,1);
    add(y,ss,0,-1);
    add(x,tt,l,1);
    add(tt,x,0,-1);
    add(x,y,r-l,1);
    add(y,x,0,-1);
}
int dfs(int x){
    bz[x]=1;
    if (x==tt) return 1;
    int r=now[x];
    while (r){
        if (!bz[go[r]]&&dis[r]&&d[x]==d[go[r]]+1){
            if (dfs(go[r])){
                dis[r]--;
                dis[fx[r]]++;
                now[x]=r;
                return 1;
            }
        }
        r=next[r];
    }
    now[x]=0;
    return 0;
}
bool change(){
    int tmp=inf,i,r;
    fo(i,ss,tt)
        if (bz[i]){
            r=h[i];
            while (r){
                if (!bz[go[r]]&&dis[r]&&d[go[r]]+1-d[i]<tmp) tmp=d[go[r]]+1-d[i];
                r=next[r];
            }
        }
    if (tmp==inf) return 0;
    fo(i,ss,tt) 
        if (bz[i]) d[i]+=tmp;
    return 1;
}
int main(){
    scanf("%d",&n);
    ss=1;tt=n*2+4;
    s=2;t=n*2+3;
    fo(i,1,n) scanf("%d%d",&x[i],&j);
    scanf("%d",&m);
    fo(i,1,m){
        scanf("%d%d",&j,&k);
        if (x[j]>x[k]) swap(j,k);
        link(j*2+2,k*2+1,0,1);
    }
    fo(i,1,n) link(i*2+1,i*2+2,1,1),link(s,i*2+1,0,1),link(i*2+2,t,0,1);
    do{
        fo(i,ss,tt) now[i]=h[i];
        fill(bz+ss,bz+tt+1,0);
        while (dfs(ss)) fill(bz+ss,bz+tt+1,0);
    }while (change());
    fo(i,ss,tt) d[i]=0;
    link(t,s,0,inf);
    do{
        fo(i,ss,tt) now[i]=h[i];
        fill(bz+ss,bz+tt+1,0);
        while (dfs(ss)) fill(bz+ss,bz+tt+1,0);
    }while (change());
    r=h[s];
    while (r){
        if (go[r]==t){
            ans=dis[r];
            break;
        }
        r=next[r];
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值