zoukankan      html  css  js  c++  java
  • [网络流系列]网络流基础

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

    这种类型的题目杀伤力很大,不论是企业测试题还是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
    ",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
    ",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
    ",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;
    }
  • 相关阅读:
    android 选择图片或拍照时旋转了90度问题
    拍照选择图片(Activity底部弹出)
    Dialog 自定义使用1
    Dialog 基本使用
    秒杀主流应用的二维码扫描
    gen already exists but is not a source folder. Convert to a source folder or rename it.
    gen already exists but is not a source folder. Convert to a source folder or rename it.
    Unable to execute dex: Multiple dex files define
    xxxx is not translated in zh-rCN, zh-rTW
    Android Application 对象介绍
  • 原文地址:https://www.cnblogs.com/light-house/p/11846223.html
Copyright © 2011-2022 走看看