前置知识

网络流求最大流的算法最少要会一种,安利一下我以前写的博客:
通俗易懂的网络流入门:EK
简单高效的Dinic算法
稍微复杂但效率很高的HLPP和ISAP算法
建议按顺序学习,并至少学完Dinic,因为Dinic不难,并且高效

无源汇的有上下界的可行流

名词解释

无源汇:不区分起点和终点的闭合图
有上下界:每条边都有一个流量上界和流量下界
可行流:此处仅考虑是否存在可行方案

解法

因为要跑网络流,所以我们引入一个起点ss和一个终点tt,接下来,考虑如何加边:
对于原图中每条边{s,t,low,up}(分别表示起点,终点,下界,上界),我们在网络流图中加入一条边{s,t,up-low}(分别表示起点,终点,边权)
对于每个点i,定义它的流量d[i]:流入的流量下界—流出的流量下界。
对于一个点i,如果d[i]>0(流入的流量多于流出的流量),则加边{ss,i,d[i]}(从ss补足缺失的部分),否则,加边{i,tt,-d[i]}(把多余的部分抽到tt)
然后从ss到tt跑最大流,如果此时能满流(从ss流出的边全部满流),则证明由于引入了额外边(上界-下界得到的边)能够使得每个点满足流入等于流出,故此时存在可行流

有源汇的有上下界的可行流

与无源汇的区别

由于原图有源s和汇t,我们只需加入一条边{t,s,0,inf}问题就转化为了无源汇的可行流

有源汇的有上下界的最大流

与可行流的区别

从ss到tt跑最大流的过程中,我们就求出了可行流,此时所有的边都已满足下界的要求,我们只需要从s到t跑一遍最大流即可在此基础上得到有源汇有上下界的最大流

完整代码(此题的)

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
inline int Read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x; 
}
const int mx=1e6;
const int inf=1<<30;
int n,m;
int s,t,ss,tt;
int top=1;
int head[mx];
int cur[mx];
struct Edge{
    int v;
    int val;
    int next;
}edge[mx<<1];
void addedge(int u,int v,int val){
    edge[++top].v=v;
    edge[top].val=val;
    edge[top].next=head[u];
    head[u]=top;
}
void add(int u,int v,int val){
    addedge(u,v,val);
    addedge(v,u,0);
}
bool inque[mx];
int deep[mx];
bool spfa(int s,int t){
    for(int i=1;i<=n+2;++i)deep[i]=inf,cur[i]=head[i];
    queue<int>q;
    q.push(s);
    deep[s]=0;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inque[u]=0;
        for(int i=head[u];i;i=edge[i].next){
            if(!edge[i].val)continue;
            int v=edge[i].v;
            if(deep[v]>deep[u]+1){
                deep[v]=deep[u]+1;
                if(!inque[v]&&v!=t){
                    q.push(v);
                    inque[v]=1;
                }
            }
        }
    }
    return deep[t]!=inf;
} 
int dfs(int flow,int u,int t){
    if(u==t)return flow;
    int used=0;
    for(int i=cur[u];i;i=edge[i].next){
        cur[u]=i;
        if(!edge[i].val)continue;
        int v=edge[i].v;
        if(deep[v]==deep[u]+1){
            int now=dfs(min(edge[i].val,flow-used),v,t);
            used+=now;
            edge[i].val-=now;
            edge[i^1].val+=now;
            if(used==flow)return flow;
        }
    }
    return used;
}
int Dinic(int s,int t){
    int maxflow=0;
    while(spfa(s,t)){
        maxflow+=dfs(inf,s,t);
    }
    return maxflow;
}
int d[mx];//每个点的流量(最小入流量-最小出流量) 
int main(){
    n=Read(),m=Read(),s=Read(),t=Read();
    ss=n+1,tt=n+2;//新的起点和终点 
    add(t,s,inf);//形成无源汇的图
    for(int i=1;i<=m;++i){
        int u=Read(),v=Read(),low=Read(),up=Read();
        add(u,v,up-low);//建多余部分的流量的图
        d[u]-=low;
        d[v]+=low; 
    } 
    int flow=0;//满流应该的流量 
    for(int i=1;i<=n;++i){
        if(d[i]>0)add(ss,i,d[i]),flow+=d[i];//少的量由起点补足  
        else add(i,tt,-d[i]);//多出来的量流给终点
    }
    int k=Dinic(ss,tt);
    if(k!=flow){//不可行 
        printf("please go home to sleep");
        return 0;
    }
    printf("%d",Dinic(s,t));
    return 0;
}

疑问

看完上述代码,你或许会疑惑:为什么跑过一遍从ss到tt的最大流之后再跑从s到t的最大流,答案不会丢失可行流的那部分,而网上许多博客也说要把t到s的边删掉,然后跑s到t的最大流,再加上t到s的边的权值
但是,实际上没有必要,因为你跑过一遍ss到tt的最大流之后,原来的边的可行流都跑到了t到s这条边的反向边上,此时从s到t跑最大流,会将可行流这部分加上去。