zoukankan      html  css  js  c++  java
  • [笔记] 网络流从入门到入土

    网络是一张有向图,每条边有一个属性:容量,即这条边最多流过的流量。

    它具有以下性质:

    • 斜对称性质:按照流函数的定义,每条边的反向边其实都有一个负的流量
    • 流量守恒定律:网络中除源点、汇点以外任何节点不存储流,其流入总量等于流出总量

    这里不得不提一下斜对称性,有向图为什么要有反向边?这是为了求解最大流。

    网络流模型可以形象的描述为:在不超过容量限制的前提下,流从源点不断产生,流经整个网络,最终全部归于汇点。

    最大流

    考虑一条残量网络上从S到T的路径,它能增加的流量就是路径上残量的最小值,若最小值大于0,则可以给网络增加流量,称这样的一条路径为一条增广路。

    显然,网络达到最大流,当且仅当网络中没有增广路。

    寻找最大流,就是一次次地寻找增广路的过程,这个过程可以使用BFS从S到T走一条增广路,这就是EK算法。

    根据斜对称性,当一条边流过w单位流量,它的反边就要流过-w单位流量,这就是一个后悔策略。

    但是EK算法存在许多不足 ,比如一次只寻找一条增广路,这些可以改进。

    Dinic算法:先用BFS将原图分层,再用DFS进行多路增广,达到较优的效果,加上当前弧优化,可以跑1e4~1e5点数的数据。

    dinic算法

    #include<iostream>
    #include<cstring>
    #include<cctype>
    #include<cstdio>
    #include<queue>
    
    using namespace std;
    
    const int MAXN=100005;
    const int INF=1<<30;
    
    int n,m,s,t;
    int mxflow;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    struct Edge{
        int next,to,w;
    }e[MAXN*10];
    int ecnt=1,head[MAXN],cur[MAXN];
    inline void add(int x,int y,int w){
        e[++ecnt].next = head[x];
        e[ecnt].to = y;
        e[ecnt].w = w;
        head[x] = ecnt;
    }
    
    int dis[MAXN];
    bool bfs(){
        queue<int> Q;
        memset(dis,0,sizeof(dis));
        Q.push(s);dis[s]=1;
        while(!Q.empty()){
            int top=Q.front();Q.pop();
            for(int i=head[top];i;i=e[i].next){
                int v=e[i].to;
                if(dis[v]||!e[i].w) continue;
                Q.push(v);dis[v]=dis[top]+1;
            }
        }
        return dis[t];
    }
    
    int dfs(int x,int val){
        if(x==t) return val;
        int used=0,tmp;
        for(int i=cur[x];i;i=e[i].next){
            int v=e[i].to;
            if(dis[v]!=dis[x]+1) continue;//
            tmp=dfs(v,min(val-used,e[i].w));
            used+=tmp;
            e[i].w-=tmp;
            e[i^1].w+=tmp;
            if(e[i].w) cur[x]=i;
            if(val==used) return val;
        }
        if(!used) dis[x]=0;
        return used;
    }
    
    void dinic(){
        while(bfs()){
            memcpy(cur,head,sizeof(head));
            mxflow+=dfs(s,INF);
        }
    }
    
    int main(){
        n=rd();m=rd();
        s=rd();t=rd();
        int x,y,w;
        for(int i=1;i<=m;i++){
            x=rd();y=rd();w=rd();
            add(x,y,w);
            add(y,x,0);
        }
        dinic();
        printf("%d
    ",mxflow);
        return 0;
    }
    View Code

    二分图最大匹配

    S向左部点连容量为1的边,右部点向T连容量为1的边,原图边改为从左往右连的容量为1的有向边。

    此时最大流=二分图最大匹配

    对二分图如此建图再使用Dinic,总复杂度O(m*sqrt(n)),这就是二分图匹配的Hopcroft-Karp算法

    #include<iostream>
    #include<cstring>
    #include<cctype>
    #include<cstdio>
    #include<queue>
    
    using namespace std;
    
    const int MAXN=20000;
    const int INF=1<<30;
    
    int n,m,s,t;
    int mxflow;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    struct Edge{
        int next,to,w;
    }e[1000005];
    int ecnt=1,head[MAXN],cur[MAXN];
    inline void add(int x,int y,int w){
        e[++ecnt].next = head[x];
        e[ecnt].to = y;
        e[ecnt].w = w;
        head[x] = ecnt;
    }
    
    int dis[MAXN];
    bool bfs(){
        queue<int> Q;
        memset(dis,0,sizeof(dis));
        Q.push(s);dis[s]=1;
        while(!Q.empty()){
            int top=Q.front();Q.pop();
            for(int i=head[top];i;i=e[i].next){
                int v=e[i].to;
                if(dis[v]||!e[i].w) continue;
                Q.push(v);dis[v]=dis[top]+1;
            }
        }
        return dis[t];
    }
    
    int dfs(int x,int val){
        if(x==t) return val;
        int used=0,tmp;
        for(int i=cur[x];i;i=e[i].next){
            int v=e[i].to;
            if(dis[v]!=dis[x]+1) continue;//
            tmp=dfs(v,min(val-used,e[i].w));
            used+=tmp;
            e[i].w-=tmp;
            e[i^1].w+=tmp;
            if(e[i].w) cur[x]=i;
            if(val==used) return val;
        }
        if(!used) dis[x]=0;
        return used;
    }
    
    void dinic(){
        while(bfs()){
            memcpy(cur,head,sizeof(head));
            mxflow+=dfs(s,INF);
        }
    }
    
    int num;
    
    int main(){
        n=rd();m=rd();num=rd();
        s=n+m+1;t=n+m+2;
        int x,y,w;
        for(int i=1;i<=num;i++){
            x=rd();y=rd();
            if(x>n||y>m) continue;
            add(x,y+n,1);
            add(y+n,x,0);
        }
        for(int i=1;i<=n;i++) add(s,i,1),add(i,s,0);
        for(int i=1;i<=m;i++) add(i+n,t,1),add(t,i+n,0);
        dinic();
        printf("%d
    ",mxflow);
        return 0;
    }
    View Code

    最小割

    网络的最大流等于最小割。

    最小费用最大流

    在每条边上新添加一个属性:花费。即这条边每通过1流量,就要增加一些代价。

    最大流大小是确定的,但是流法却可以有很多种,最小费用最大流就是求解在满足最大流的前提下,花费最小的流。

    可以将EK算法中寻找增广路的BFS改为SPFA,这样保证既能找到增广路,也满足最小花费。

    EK+SPFA算法

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    
    using namespace std;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    const int MAXN=5005;
    
    struct Edge{
        int next,to,w,f;
    }e[500005<<1];
    int ecnt=1,head[MAXN];
    inline void add(int x,int y,int f,int w){
        e[++ecnt].next = head[x];
        e[ecnt].to = y;
        e[ecnt].f = f;
        e[ecnt].w = w;
        head[x] = ecnt;
    }
    
    int n,m,s,t;
    
    int dis[MAXN],flow[MAXN],pre[MAXN],inq[MAXN];
    queue<int> Q;
    bool spfa(){
        memset(dis,0x3f,sizeof(dis));
        dis[s]=0;flow[s]=1<<30;
        Q.push(s);
        while(!Q.empty()){
            int top=Q.front();Q.pop();inq[top]=0;
            for(int i=head[top];i;i=e[i].next){
                int v=e[i].to;
                if(dis[v]<=dis[top]+e[i].w||!e[i].f) continue;
                dis[v]=dis[top]+e[i].w;
                flow[v]=min(flow[top],e[i].f);
                pre[v]=i;
                if(!inq[v]) Q.push(v),inq[v]=1;
            }
        }
        return dis[t]!=0x3f3f3f3f;
    }
    
    int ans_flow,ans_cost;
    void increase(){
        int cur=t,mx=flow[t];
        while(cur!=s){
            int i=pre[cur];
            e[i].f-=mx;
            e[i^1].f+=mx;
            cur=e[i^1].to;
        }
        ans_flow+=mx;ans_cost+=dis[t]*mx;
    }
    
    
    void EK(){
        while(spfa()) increase();
    }
    
    
    int main(){
        n=rd();m=rd();s=rd();t=rd();
        int x,y,f,w;
        for(int i=1;i<=m;i++){
            x=rd();y=rd();f=rd();w=rd();
            add(x,y,f,w);add(y,x,0,-w);
        }
        EK();
        cout<<ans_flow<<" "<<ans_cost<<endl;
        return 0;
    }
    View Code

    zkw费用流

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue> 
    using namespace std;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    const int MAXN=5005;
    const int M=100005;
    const int INF=1<<30;
     
    int n,m; 
     
    struct Edge{
        int next,to,f,w;
    }e[M<<1];
    int ecnt=1,head[MAXN],cur[MAXN];
    void adds(int x,int y,int f,int w){
        e[++ecnt].next = head[x];
        e[ecnt].to = y;
        e[ecnt].f = f;
        e[ecnt].w = w;
        head[x] = ecnt;
    }
    void add(int x,int y,int f,int w){
        adds(x,y,f,w);adds(y,x,0,-w);
    }
    int d[MAXN];
    bool vis[MAXN];
    int aug(int x,int t,int flow){
        if(x==t) return flow;
        vis[x]=1;int tmp;
        for(int v,&i=cur[x];i;i=e[i].next){
            if(!e[i].f||vis[v=e[i].to]||d[x]!=d[v]+e[i].w)continue;
            tmp=aug(v,t,min(flow,e[i].f));
            if(tmp)return e[i].f-=tmp,e[i^1].f+=tmp,tmp;
        }
        return 0;
    }
    bool update(){
         int tmp=INF;
         for(int i=1;i<=n;i++){
             if(!vis[i]) continue;
             for(int v,j=head[i];j;j=e[j].next)
                 if(e[j].f&&!vis[v=e[j].to])
                     tmp=min(tmp,d[v]+e[j].w-d[i]);
         }
         if(tmp==INF) return true;
         for(int i=1;i<=n;i++)
             if(vis[i]) vis[i]=0,d[i]+=tmp;
         return false;
     }
    int mxflow,mncost;
    void zkw(int s,int t){
        int tmp;
        while(1){
            memcpy(cur,head,sizeof((head)));
            while(tmp=aug(s,t,INF)){
                mxflow+=tmp;
                mncost+=tmp*d[s];
                memset(vis,0,sizeof(vis));
            }
            if(update())break;
        }
    }
         
    int main(){    
           int S,T;
        n=rd();m=rd();S=rd();T=rd();
        int x,y,f,w; 
        while (m--){
            x=rd();y=rd();f=rd();w=rd();
            add(x,y,f,w); 
        }
        zkw(S,T);
        printf("%d %d",mxflow,mncost);
        return 0;
      }
    View Code

    无源汇有上下界可行流

    为了满足流量守恒,没有源汇的网络显然是一个循环网络。

    对于一条弧,可以拆成必要弧和附加弧,必要弧就是这条弧的下界,附加弧是可以在必要弧基础上增广的容量。

    必要弧为下界,添加附加弧和附加源汇,看能否跑满出边。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    
    using namespace std;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    const int MAXN=256;
    const int M=100005;
    const int INF=1<<30;
    
    struct Edge{
        int next,to,f;
    }e[M<<1];
    int ecnt=1,head[MAXN];
    inline void add(int x,int y,int f){
        e[++ecnt].next = head[x];
        e[ecnt].f = f;
        e[ecnt].to =y;
        head[x] = ecnt;
    }
    int low[M];
    
    int S,T;
    
    int n,m;
    int d[MAXN];
    
    int dep[MAXN];
    queue<int> Q;
    bool bfs(){
        memset(dep,0,sizeof(dep));
        Q.push(S);dep[S]=1;
        while(!Q.empty()){
            int top=Q.front();Q.pop();
            for(int i=head[top];i;i=e[i].next){
                int v=e[i].to;
                if(dep[v]||!e[i].f) continue;
                dep[v]=dep[top]+1;Q.push(v);
            }
        }
        return dep[T];
    }
    int cur[MAXN];
    int dfs(int x,int flow){
        if(x==T) return flow;
        int used=0,tmp;
        for(int i=cur[x];i;i=e[i].next){
            int v=e[i].to;
            if(dep[v]!=dep[x]+1) continue;
            if(!e[i].f) continue;
            tmp=dfs(v,min(e[i].f,flow-used));
            used+=tmp;e[i].f-=tmp;e[i^1].f+=tmp;
            if(used==flow) return flow;
            if(e[i].f) cur[x]=i;
        }
        if(!used) dep[x]=-1;
        return used;
    }
    
    int mxflow;
    void dinic(){
        while(bfs()){
            memcpy(cur,head,sizeof(head));
            mxflow+=dfs(S,INF);
        }
    }
            
    
    int main(){
        n=rd();m=rd();
        int x,y,l,r;
        for(int i=1;i<=m;i++){
            x=rd();y=rd();l=rd();r=rd();
            add(x,y,r-l);add(y,x,0);
            d[x]-=l;d[y]+=l;low[i]=l;
        }
        S=n+1;T=n+2;
        int sum=0;
        for(int i=1;i<=n;i++){
            if(d[i]<0){
                add(i,T,-d[i]);
                add(T,i,0);
            }
            if(d[i]>0){
                add(S,i,d[i]);
                add(i,S,0);
                sum+=d[i];
            }
        }
        dinic();
        if(mxflow<sum) return puts("NO"),0;
        puts("YES");
        for(int i=1;i<=m;i++){
            printf("%d
    ",low[i]+e[i<<1|1].f);
        }
        return 0;
    }
    View Code

    有源汇有上下界可行流

    从T向S连一条下界为0,上界为正无穷的边即可转化为无源汇有上下界最大流。

    有源汇有上下界最大流

    先求出可行流,再从S到T求一次最大流,加上即可。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    
    using namespace std;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    const int MAXN=256;
    const int M=100005;
    const int INF=1<<30;
    
    struct Edge{
        int next,to,f;
    }e[M<<1];
    int ecnt=1,head[MAXN];
    inline void add(int x,int y,int f){
        e[++ecnt].next = head[x];
        e[ecnt].f = f;
        e[ecnt].to =y;
        head[x] = ecnt;
    }
    int low[M];
    
    int S,T,SS,TT;
    
    int n,m;
    int d[MAXN];
    
    int dep[MAXN];
    queue<int> Q;
    bool bfs(int x,int y){
        memset(dep,0,sizeof(dep));
        Q.push(x);dep[x]=1;
        while(!Q.empty()){
            int top=Q.front();Q.pop();
            for(int i=head[top];i;i=e[i].next){
                int v=e[i].to;
                if(dep[v]||!e[i].f) continue;
                dep[v]=dep[top]+1;Q.push(v);
            }
        }
        return dep[y];
    }
    int cur[MAXN];
    int dfs(int x,int flow,int arr){
        if(x==arr) return flow;
        int used=0,tmp;
        for(int i=cur[x];i;i=e[i].next){
            int v=e[i].to;
            if(dep[v]!=dep[x]+1) continue;
            if(!e[i].f) continue;
            tmp=dfs(v,min(e[i].f,flow-used),arr);
            used+=tmp;e[i].f-=tmp;e[i^1].f+=tmp;
            if(used==flow) return flow;
            if(e[i].f) cur[x]=i;
        }
        if(!used) dep[x]=-1;
        return used;
    }
    
    int mxflow;
    void dinic(int x,int y){
        while(bfs(x,y)){
            memcpy(cur,head,sizeof(head));
            mxflow+=dfs(x,INF,y);
        }
    }
            
    
    int ans;
    
    int main(){
    //    freopen("1.in","r",stdin);
        n=rd();m=rd();S=rd();T=rd();
        int x,y,l,r;
        for(int i=1;i<=m;i++){
            x=rd();y=rd();l=rd();r=rd();
            add(x,y,r-l);add(y,x,0);
            d[x]-=l;d[y]+=l;low[i]=l;
        }
        SS=n+1;TT=n+2;
        
        int sum=0;
        for(int i=1;i<=n;i++){
            if(d[i]<0){
                add(i,TT,-d[i]);
                add(TT,i,0);
            }
            if(d[i]>0){
                add(SS,i,d[i]);
                add(i,SS,0);
                sum+=d[i];
            }
        }
        add(T,S,INF);add(S,T,0);
        int tmp=ecnt;
        dinic(SS,TT);
        ans+=e[tmp].f;
        for(int i=head[SS];i;i=e[i].next){
            if(e[i].f) return puts("please go home to sleep"),0;
        }    
        mxflow=0;
        head[S]=e[head[S]].next;
        head[T]=e[head[T]].next;
        dinic(S,T);
        cout<<ans+mxflow;
        return 0;
    }
    View Code

    有源汇有上下界最小流

    考虑如何将一些“非必要边”删掉,注意到我们并没有这样的算法,但是根据斜对称性,反边增加流量就是正边减少流量,于是可以在残量网络上求出T到S的最大流,这就是可行流最多可以减去的部分。

    先求出可行流,再从T到S求一次最大流,减去即可。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    
    using namespace std;
    
    inline int rd(){
        int ret=0,f=1;char c;
        while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
        while(isdigit(c))ret=ret*10+c-'0',c=getchar();
        return ret*f;
    }
    
    const int MAXN=50500;
    const int M=200005;
    const int INF=1<<30;
    
    struct Edge{
        int next,to,f;
    }e[M<<1];
    int ecnt=1,head[MAXN];
    inline void add(int x,int y,int f){
        e[++ecnt].next = head[x];
        e[ecnt].f = f;
        e[ecnt].to =y;
        head[x] = ecnt;
    }
    int low[M];
    
    int S,T,SS,TT;
    
    int n,m;
    int d[MAXN];
    
    int dep[MAXN];
    queue<int> Q;
    bool bfs(int x,int y){
        memset(dep,0,sizeof(dep));
        Q.push(x);dep[x]=1;
        while(!Q.empty()){
            int top=Q.front();Q.pop();
            for(int i=head[top];i;i=e[i].next){
                int v=e[i].to;
                if(dep[v]||!e[i].f) continue;
                dep[v]=dep[top]+1;Q.push(v);
            }
        }
        return dep[y];
    }
    int cur[MAXN];
    int dfs(int x,int flow,int arr){
        if(x==arr) return flow;
        int used=0,tmp;
        for(int i=cur[x];i;i=e[i].next){
            int v=e[i].to;
            if(dep[v]!=dep[x]+1) continue;
            if(!e[i].f) continue;
            tmp=dfs(v,min(e[i].f,flow-used),arr);
            used+=tmp;e[i].f-=tmp;e[i^1].f+=tmp;
            if(used==flow) return flow;
            if(e[i].f) cur[x]=i;
        }
        if(!used) dep[x]=-1;
        return used;
    }
    
    int mxflow;
    void dinic(int x,int y){
        while(bfs(x,y)){
            memcpy(cur,head,sizeof(head));
            mxflow+=dfs(x,INF,y);
        }
    }
            
    
    int ans;
    
    int main(){
    //    freopen("8.in","r",stdin);
        n=rd();m=rd();S=rd();T=rd();
        int x,y,l,r;
        for(int i=1;i<=m;i++){
            x=rd();y=rd();l=rd();r=rd();
            add(x,y,r-l);add(y,x,0);
            d[x]-=l;d[y]+=l;low[i]=l;
        }
        SS=n+1;TT=n+2;
        
        int sum=0;
        for(int i=1;i<=n;i++){
            if(d[i]<0){
                add(i,TT,-d[i]);
                add(TT,i,0);
            }
            if(d[i]>0){
                add(SS,i,d[i]);
                add(i,SS,0);
                sum+=d[i];
            }
        }
        add(T,S,INF);add(S,T,0);
        int tmp=ecnt;
        dinic(SS,TT);
        ans+=e[tmp].f;
        for(int i=head[SS];i;i=e[i].next){
            if(e[i].f) return puts("please go home to sleep"),0;
        }    
        mxflow=0;
        head[S]=e[head[S]].next;
        head[T]=e[head[T]].next;
        dinic(T,S);
        cout<<ans-mxflow;
        return 0;
    }
    View Code

    本文来自博客园,作者:GhostCai,转载请注明原文链接:https://www.cnblogs.com/ghostcai/p/9484206.html

  • 相关阅读:
    [Swift]LeetCode922.按奇偶排序数组 II | Sort Array By Parity II
    [Swift]LeetCode921.使括号有效的最少添加 | Minimum Add to Make Parentheses Valid
    [Swift实际操作]八、实用进阶-(5)通过间接代理进行对象间的消息传递
    [Swift]LeetCode927. 三等分 | Three Equal Parts
    [Swift]LeetCode928. 尽量减少恶意软件的传播 II | Minimize Malware Spread II
    我是大牛,我自豪
    程序员拓展人脉的重要性
    程序员拓展人脉的重要性
    2013年总结(3)-活动篇
    2013年总结(3)-活动篇
  • 原文地址:https://www.cnblogs.com/ghostcai/p/9484206.html
Copyright © 2011-2022 走看看