[网络流系列]网络流基础

网络流是一种独特的图论模型题目,一般一些神仙提经过很精巧的模型转化,就会变成一道网络流的题目。

这种类型的题目杀伤力很大,不论是企业测试题还是oi竞赛题,只要涉及到模型转化就会变成毒瘤题。

这类题目的解法我会在模型篇讨论,这一篇主要还是以网络流基础为主。

本博客无图,真不是我嫌画得累,只是讲这个东西其实真用不着图,

如果不清楚网络流概念的请先自行查找相关资料。

我们首先来看最大流问题:
给定你一张图,图中的每一条边都有一个流量限制,再给你一个源点s和汇点t,源点的储水量为+∞,问你从源点s最多能流多少水到汇点t。

解决这类问题的常用算法是Edmond-Karp算法和Dinic算法,它们的核心思路都是寻找增广路

给出增广路的概念:

若流过从s到t中一条路径,流量会增加,则称这条路是一条增广路。

简单分析,我们一条条的找增广路流过去,如果找不到更多增广路了,那我们流量就已经最大了。

简单证明:没有增广路了,流量就不会增加了...所以流量已经最大了...=w=

怎么找增广路呢?这篇博客只会介绍Dinic算法,这也是最常用,平均效率最高的网络流算法。

其实网络流的算法除了上面两种外还有其他的,不过不常用,本文就不多赘述,学有余力的朋友可以去查找相关资料学习。

Dinic算法其实想法很简单,我们使用bfs把流图分层,然后使用dfs寻找增广路。

与Edmond-Karp的区别就是,Dinic是多路增广,而EK是单路增广。

我们建网络图的时候,给每一条边加一条反向的边权为0的边。

加这些边有什么用处呢?这其实是一个很巧妙的想法。

首先呢,流图满足流量守恒,我们添加反向流量,其实就是提供后续调整流的机会。

我们并不知道怎样流增广路可以得到最大流,我们需要每种情况都流一次,然而我们并不可能用回溯来回去换情况流,这样的时间复杂度是指数级的。这个思路的流程是这样的:我们每找到一条增广路,就把路径上的容量减去该路上的最小流量,为什么减的是最小流量呢?因为我们这一条路径最多能流的流量就是这一条路的最小流量,流一遍后我们剩下的容量就要减那么多。然后我们再在它的反向路径上加上最小流量。为什么要加上这最小流量呢?

易懂一些的理解就是,我们第二次增广,再流过这条边的时候,就相当于把走正向边走过来用掉的流量还回去,不走它了,这样就相当于我们换了一种情况来走了(没走这一条路,走了另外的路)

然后就是算法流程了,首先我们要用bfs来寻找增广路,在bfs的过程中给流图分层。

对于任意的一个点x,我们从s走到x每多走了一个点,层数就加一。

分完层后,对于从x到y的路径,只要满足dep[y]==dep[x]+1,这条路径就在一条最短增广路上面。

我们每次寻找最短的增广路进行增广,如果没法增广了,就可以判断是下面两种情况了:
1. 找到了最大流 2. 存在更长的增广路可以增广

如果是情况2,我们就继续bfs。每次完成后最短增广路长度+1。由于最短路是<n的,所以我们最多只需要执行n-1次bfs就可以得到答案。

我们每次bfs后,都会得到该最短长度的增广路,然后我们用dfs进行增广操作,每次dfs进行多路增广。

最后我们得到最大流的理论复杂度是$O(N^2M)$的,如果是稠密图,即边很多的情况下,Dinic是要比$O(NM^2)$的EK算法跑得快很多的。

接下来给出代码,看不懂的就看注释吧:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int data=0,w=1;char ch=0;
    while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
    return data*w;
}
const int N=1e5+10;
const int inf=1<<30;
struct Edge{
    int nxt,to,val;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
    #define val(x) e[x].val
}e[N<<1];
int head[N],tot=1,maxflow;
int n,m,s,t;
int dep[N],inque[N];
inline void addedge(int f,int t,int v){
    nxt(++tot)=head[f];to(tot)=t;val(tot)=v;head[f]=tot;
}
inline int bfs(){
    memset(inque,0,sizeof inque);
    memset(dep,0x3f,sizeof dep);
    dep[s]=0;
    queue<int> q;
    q.push(s);
    while(q.size()){
        int x=q.front();q.pop();
        inque[x]=0;
        for(int i=head[x];i;i=nxt(i)){
            int y=to(i);
            if(dep[y]>dep[x]+1&&val(i)){//最短且增广 
                dep[y]=dep[x]+1;
                if(!inque[y]){
                    q.push(y);inque[y]=1;
                }
            }
        }
    }return dep[t]!=0x3f3f3f3f;
}
inline int dfs(int x,int flow){
    int rlow=0;
    if(x==t){
        maxflow+=flow;return flow;
    }
    int used=0;
    for(int i=head[x];i;i=nxt(i)){
        int y=to(i);
        if(dep[y]==dep[x]+1&&val(i)){//最短增广路 
            rlow=dfs(y,min(flow-used,val(i)));//计算剩余流量 
            if(rlow){
                used+=rlow;
                val(i)-=rlow;//正向边容量 
                val(i^1)+=rlow;//反向边容量
                if(used==flow)break;//流量满了,不找了 
            }
        }
    }if(used==0)dep[x]=0;
    return used;
}
inline int Dinic(){
    while(bfs())dfs(s,inf);
    return maxflow;
}
int main(){
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read(),z=read();
        addedge(x,y,z);addedge(y,x,0);
    }
    printf("%d\n",Dinic());
    return 0;
}

二分图最大匹配

做法?我们新建一个超级源点s和超级汇点t。然后s和一边连边,t和另一边连边,再跑一遍最大流即可。

记录方案?我们判断边是否有流量即可。怎么判断边是否有流量?判断反向边的流量是不是0就好了。

例题:LuoguOJ P2756

对于这道题,我们把可以配合的外籍和英籍连边,s向外籍连边,英籍再向t连边跑一遍最大流即可,注意每条边的容量都是1,因为我们只能一一对应地匹配。

给出代码:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int data=0,w=1;char ch=0;
    while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
    return data*w;
}
const int N=1e5+10;
const int inf=1<<30;
struct Edge{
    int nxt,to,val;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
    #define val(x) e[x].val
}e[N<<1];
int head[N],tot=1,maxflow;
inline void addedge(int f,int t,int val){
    nxt(++tot)=head[f];to(tot)=t;val(tot)=val;head[f]=tot;
}
int n,m,s,t;
int inque[N],dep[N];
int bfs(){
    memset(inque,0,sizeof inque);
    memset(dep,0x3f,sizeof dep);
    queue<int> q;q.push(s);dep[s]=1;
    while(q.size()){
        int x=q.front();q.pop();inque[x]=0;
        for(int i=head[x];i;i=nxt(i)){
            int y=to(i);
            if(dep[y]>dep[x]+1&&val(i)){
                dep[y]=dep[x]+1;
                if(!inque[y]){
                    q.push(y);inque[y]=1;
                }
            }
        }
    }return dep[t]!=0x3f3f3f3f;
}
int dfs(int x,int flow){
    int rlow=0;
    if(x==t){
        maxflow+=flow;return flow;
    }
    int used=0;
    for(int i=head[x];i;i=nxt(i)){
        int y=to(i);
        if(dep[y]==dep[x]+1&&val(i)){
            rlow=dfs(y,min(flow-used,val(i)));
            if(rlow){
                used+=rlow;
                val(i)-=rlow;
                val(i^1)+=rlow;
                if(used==flow)break;
            }
        }
    }if(used==0)dep[x]=0;
    return used;
}
int Dinic(){
    while(bfs())dfs(s,inf);
    return maxflow;
}
int main(){
    m=read();n=read();//奇葩题目非要m,n反过来 
    s=n+1,t=n+2;
    for(int i=1;i<=m;i++)
        addedge(s,i,1),addedge(i,s,0);//外国 
    for(int i=m+1;i<=n;i++)
        addedge(i,t,1),addedge(t,i,0);//英国 
    while(1){
        int x=read(),y=read();
        if(x==-1&&y==-1)break;
        addedge(x,y,1);addedge(y,x,0);
    }
    int ans=Dinic();
    if(ans==0)
        puts("No Solution!");
    else{
        printf("%d\n",ans);
        for(int i=2;i<=tot;i+=2){
            if(to(i)!=s&&to(i^1)!=s)
                if(to(i)!=t&&to(i^1)!=t)
                    if(val(i^1))
                        printf("%d %d\n",to(i^1),to(i));
        }
    }
    return 0;
}

然后是最小费用最大流

待填坑,先放代码。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
struct Edge{
    int nxt,to,val,w;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
    #define v(x) e[x].val
    #define w(x) e[x].w
}e[maxn<<1];
const int inf=1<<30;
int head[maxn];
int tot=1;
int n,m,s,t;
int cost,maxflow;
int vis[maxn],dis[maxn],cur[maxn];
inline void addedge(int f,int t,int val,int ww){
    nxt(++tot)=head[f];to(tot)=t;v(tot)=val;w(tot)=ww;head[f]=tot;
}
bool spfa(){
    for(int i=0;i<=n;i++)cur[i]=head[i],dis[i]=0x3f3f3f3f,vis[i]=0;
    dis[s]=0;vis[s]=1;
    queue<int> q;q.push(s);
    while(q.size()){
        int x=q.front();q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=nxt(i)){
            int y=to(i);
            if(dis[y]>dis[x]+w(i) && v(i)){
                dis[y]=dis[x]+w(i);
                if(!vis[y]){
                    q.push(y);
                    vis[y]=1;
                }
            }
        }
    }
    return dis[t]!=0x3f3f3f3f;
}
inline int Fmin(int x,int y){
    return (((y-x)>>31)&(x^y))^x;
}
inline int Fmax(int x,int y){
    return (((y-x)>>31)&(x^y))^y;
}
int dfs(int x,int flow){
    if(x==t){
        vis[t]=1;maxflow+=flow;return flow;
    }
    int used=0;
    vis[x]=1;
    for(int i=cur[x];i;i=nxt(i)){
        cur[x]=i;
        int y=to(i);
        if((!vis[y]||y==t) && v(i) && dis[y]==dis[x]+w(i)){
            int minflow=dfs(y,Fmin(flow-used,v(i)));
            if(minflow)cost+=w(i)*minflow,v(i)-=minflow,v(i^1)+=minflow,used+=minflow;
            if(used==flow)break;
        }
    }
    return used;
}
int MinCostMaxFlow(){
    while(spfa()){
        vis[t]=1;
        while(vis[t]){
            memset(vis,0,sizeof vis);
            dfs(s,inf);
        }
    }
    return maxflow;
}
inline int read(){
    int data=0,w=1;char ch=0;
    while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
    return data*w;
}
int main(){
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;i++){
        int x=read();int y=read();int val=read();int w=read();
        addedge(x,y,val,w);
        addedge(y,x,0,-w);
    }
    maxflow=MinCostMaxFlow();
    printf("%d %d",maxflow,cost);
    return 0;
}
posted @ 2019-11-14 16:54  LightHouseOfficial  阅读(...)  评论(... 编辑 收藏