网络流(4)——最小流

本文介绍了最小流的概念,讨论了如何构建最小流,包括流量平衡条件和流量守恒。通过举例展示了在网络图中求解最小流的方法,如在加了附加源汇的图中利用二分法。同时,文章提供了两个OI题目作为实例,解释了如何在实际问题中应用最小流算法解决求最小路径和士兵占领棋盘问题。

啊,学渣苦,学渣累

什么是最小流

emm…
顾名思义,最小流的前提是有下界(不需要黑曜石)

如何构建最小流

emm…好问题。
我们需要先构建一个无源汇可行流
emm…
我们先这样定义一个网络图:

名称意义
E 边集
G点集
B(i,j)ij的下界
C(i,j)ij的上界
C(i,j)ij的上界减下界
g(i,j)ij的可行流量差值
f(i,j)ij的实际流量

推导:首先被满足的是一个等式——流量平衡条件:

(u,i)ϵEf(u,i)=(i,v)ϵEf(i,v)

=

以及如何表示f(i,j)

f(i,j)=B(i,j)+g(i,j)

代入流量平衡条件,有:
(u,i)ϵE[B(u,i)+g(u,i)]=(i,v)ϵE[B(i,v)+g(i,v)]

移项,得:
(u,i)ϵEB(u,i)(i,v)ϵEB(i,v)=(i,v)ϵEg(i,v)(u,i)ϵEg(u,i)

我们现在引进一个新变量M(i),使得:
M(i)=(u,i)ϵEB(u,i)(i,v)ϵEB(i,v)

很显然你的M(i)其实是一个定值,我们可以分两种情况讨论:
M(i)是一个非负数时,新建一个附加源S0,使得:
C(S0,i)=M(i)

M(i)是一个负数时,新建一个附加源T0,使得:
C(i,T0)=M(i)

所以如果对于任意原图节点i,均有{g(S0,i)=C(S0,i)g(i,T0)=C(i,T0)

那么说明在有下界限制条件下每个节点均能满足流量守恒,则C的一个可行流g一定对应的是原图中的一个可行流f
然后你还可以判一判这个网络究竟有没有满足下界条件的可行流
(虽然很多OI题并没有打算让你干这个)

好所以我们现在来考虑如何求最小流

如果我们在加了附加源汇的图中,连一条T0S0的边,设下界为g(S0,T0),那么当前图就是一个循环流量图了
然后你也可以通过二分附加边的权值来确定这个图的最大可行流
什么?最小流怎么办?当然是二分上界辣

这个的复杂度是要带log
所以我们考虑可行流的流量显然是边(T0,S0)的反向边的流量
那么接下来怎么做呢?
如果是求最大流你顺着S0,T0再跑一次好像就得了(???)

如果是最小流该怎么办?

对于最小流,考虑可行流可能多流了S0T0的部分流量,我们反着跑最大流即可
什么?直接退流可能会使流不满足下界?
我们将第一次跑流时超级源点和超级汇点连出的边及其反向边都删掉,并且再删掉(T0,S0)这条边,这样保证流不会顺着这些边退回去导致不合法。

当然是很水的例题

(1)BZOJ2502 清理雪道
滑雪场坐落在FJ省西北部的若干座山上。
从空中鸟瞰,滑雪场可以看作一个有向无环图,每条弧代表一个斜坡(即雪道),弧的方向代表斜坡下降的方向。
你的团队负责每周定时清理雪道。你们拥有一架直升飞机,每次飞行可以从总部带一个人降落到滑雪场的某个地点,然后再飞回总部。从降落的地点出发,这个人可以顺着斜坡向下滑行,并清理他所经过的雪道。
由于每次飞行的耗费是固定的,为了最小化耗费,你想知道如何用最少的飞行次数才能完成清理雪道的任务。

有向图求最小路径数,可以覆盖所有边至少一次
直接套用上述做法即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
    int i=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch);ch=getchar())
        if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar())
        i=(i<<3)+(i<<1)+(ch^48);
    return i*f;
}
int buf[1024];
inline void write(int x){
    if(!x){putchar('0');return ;}
    if(x<0){putchar('-');x=-x;}
    while(x){buf[++buf[0]]=x%10,x/=10;}
    while(buf[0]) putchar(buf[buf[0]--]+48);
    return ;
}
#define stan 111
#define sten 1111111
int ans,S,T,n,m,k,s,t,x,y,tot1,tot2;
int tot,first[sten],nxt[sten],goal[sten],wide[sten],M[stan];
int level[sten];
inline void addedge(int a,int b,int c){
    nxt[++tot]=first[a];first[a]=tot;goal[tot]=b;wide[tot]=c;
    nxt[++tot]=first[b];first[b]=tot;goal[tot]=a;wide[tot]=0;
    return ;
}
bool bfs(){
    static int que[sten];
    int q=1;
    for(int i=0;i<=n+3;++i)
        level[i]=-1;
    level[S]=1;que[q]=S;
    for(int i=1;i<=q;++i){
        int u=que[i];
        for(int p=first[u];p;p=nxt[p])
            if(wide[p]&&level[goal[p]]==-1){
                level[goal[p]]=level[u]+1;
                que[++q]=goal[p];
                if(goal[p]==T) return true;
            }
    }
    return false;
}
int dfs(int u,int flow){
    if(u==T) return flow;
    int ret=0,data;
    for(int p=first[u];p;p=nxt[p])
        if(wide[p]&&level[goal[p]]==level[u]+1){
            data=dfs(goal[p],min(flow-ret,wide[p]));
            if(data){
                wide[p]-=data;
                wide[p^1]+=data;
                ret+=data;
                if(ret==flow) return ret;
            }
        }
    level[u]=-1;
    return ret;
}
int solve(){
    int ret=0;
    while(bfs())
        ret+=dfs(S,999999999);
    return ret;
}
signed main(){
    n=read();
    s=0;t=n+1;
    S=n+2;T=n+3;tot=1;
    for(int i=1;i<=n;++i){
        x=read();
        addedge(s,i,999999999);
        addedge(i,t,999999999);
        for(int j=1;j<=x;++j){
            y=read();
            addedge(i,y,999999999-1);
            --M[i];++M[y];
        }
    }
    for(int i=1;i<=n;++i){
        if(M[i]>0) addedge(S,i,M[i]);
        if(M[i]<0) addedge(i,T,-M[i]);
    }
    addedge(t,s,999999999);
    solve();
    for(int p=first[S];p;p=nxt[p])
        wide[p]=wide[p^1]=0;
    for(int p=first[T];p;p=nxt[p])
        wide[p]=wide[p^1]=0;
    ans=wide[tot];
    wide[tot-1]=wide[tot]=0;
    S=t;T=s;
    ans-=solve();
    write(ans);
    return 0;
}

(2)BZOJ1458 士兵占领
有一个M * N的棋盘,有的格子是障碍。现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵。我们称这些士兵占领了整个棋盘当满足第i行至少放置了Li个士兵, 第j列至少放置了Cj个士兵。现在你的任务是要求使用最少个数的士兵来占领整个棋盘。

注意有的边下界是0

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
    int i=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch);ch=getchar())
        if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar())
        i=(i<<3)+(i<<1)+(ch^48);
    return i*f;
}
int buf[1024];
inline void write(int x){
    if(!x){putchar('0');return ;}
    if(x<0){putchar('-');x=-x;}
    while(x){buf[++buf[0]]=x%10,x/=10;}
    while(buf[0]) putchar(buf[buf[0]--]+48);
    return ;
}
#define stan 222
#define sten 1111111
int ans,S,T,n,m,k,s,t,x,y,tot1,tot2;
int tot,first[sten],nxt[sten],goal[sten],wide[sten];
int level[sten];
bool ac[stan][stan];
inline void addedge(int a,int b,int c){
    nxt[++tot]=first[a];first[a]=tot;goal[tot]=b;wide[tot]=c;
    nxt[++tot]=first[b];first[b]=tot;goal[tot]=a;wide[tot]=0;
    return ;
}
bool bfs(){
    static int que[sten];
    int q=1;
    for(int i=0;i<=T;++i)
        level[i]=-1;
    level[S]=1;que[q]=S;
    for(int i=1;i<=q;++i){
        int u=que[i];
        for(int p=first[u];p;p=nxt[p])
            if(wide[p]&&level[goal[p]]==-1){
                level[goal[p]]=level[u]+1;
                que[++q]=goal[p];
                if(goal[p]==T) return true;
            }
    }
    return false;
}
int dfs(int u,int flow){
    if(u==T) return flow;
    int ret=0,data;
    for(int p=first[u];p;p=nxt[p])
        if(wide[p]&&level[goal[p]]==level[u]+1){
            data=dfs(goal[p],min(flow-ret,wide[p]));
            if(data){
                wide[p]-=data;
                wide[p^1]+=data;
                ret+=data;
                if(ret==flow) return ret;
            }
        }
    level[u]=-1;
    return ret;
}
int solve(){
    int ret=0;
    while(bfs())
        ret+=dfs(S,999999999);
    return ret;
}
signed main(){
    m=read();n=read();k=read();
    S=n+m+2;T=n+m+3;tot=1;
    s=0;t=n+m+1;
    for(int i=1;i<=m;++i){
        x=read();
        addedge(s,i,999999999);
        addedge(S,i,x);
        tot1+=x;
    }
    addedge(s,T,tot1);
    for(int i=1;i<=n;++i){
        x=read();
        addedge(m+i,t,999999999);
        addedge(m+i,T,999999999);
        tot2+=x;
    }
    addedge(t,S,tot2);
    for(int i=1;i<=k;++i){
        x=read();y=read();
        ac[x][y]=true;
    }
    for(int i=1;i<=m;++i)   
        for(int j=1;j<=n;++j)
            if(!ac[i][j])
                addedge(i,j+m,1);
    addedge(S,t,tot2);
    ans=solve();
    if(ans<max(tot1,tot2)){
        puts("JIONG");
        return 0;
    }else{
        for(int p=first[S];p;p=nxt[p])
            wide[p]=wide[p^1]=0;
        for(int p=first[T];p;p=nxt[p])
            wide[p]=wide[p^1]=0;
        S=t;T=s;
        ans-=solve();
        write(ans);
        return 0;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值