zoukankan      html  css  js  c++  java
  • 最大流和最小费用流

    最大流

    在图网络中,找到从源点到汇点的最大流量

    基本思路:对任一一个可行流,求出其残余网络,在残余网络中,找到一条增广路径,确定该路径的流量(min(c(i,j))f,每条边的流量减去f,建立反向边,流量为f。

    因此我们可以用dfs找一条路径,对路径上流量进行修改,建立反向边,形成新的残余网络。再进行一次dfs,多次dfs后我们可以得到最终结果。

    时间复杂度分析:每次dfs最少增加1流量,网络总流量为C,一次dfs复杂度为O(n+m),总的时间复杂度为C*(n+m)=C*n^2

    一个修改策略是每次走最短的次数,使得可以从源点到汇点。因此可以将上述dfs改成bfs实现,该算法称为EK算法,时间复杂度为nm^2

    另一种策略是对网络分层--Dinic算法

    从源点到顶点相同步数的顶点归为同一层,这一过程可以通过bfs实现。

    紧接着,我们用dfs寻找增广路径(用栈实现可以保存路径),要求路径每次都是从前一层到下一层。那么只要最后达到汇点所在的层,本次dfs即可结束

    EK算法实现:(POJ1273)

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    int G[300][300];
    int prev[300];//每个结点的前驱节点
    bool vis[300];
    int n,m;
    const int inf=0x3f3f3f3f;
    int Augment()
    {
        deque<int>q;
        memset(prev,0,sizeof(prev));
        memset(vis,0,sizeof(vis));
        q.push_back(1);
        prev[1]=0;
        vis[1]=1;
        bool Find=0;
        int v;
        while(!q.empty()) //找到一条可行的路
        {
            v=q.front();
            q.pop_front();
            for(int i=1;i<=m;i++)
            {
                if(G[v][i]>0&&!vis[i])//有容量可以走
                {
                    prev[i]=v;
                    vis[i]=1;
                    if(i==m) //达到最后一个节点
                    {
                        Find=1;
                        q.clear();
                        break;
                    }
                    else
                        q.push_back(i);
                }
            }
        }
        if(!Find)
            return 0;
        int mi=inf;
        v=m;
        while(prev[v]) //从最后一个节点开始找前驱节点
        {
            mi=min(mi,G[prev[v]][v]); //流量为路径中所有流量中最小的
            v=prev[v];
        }
        //添加反向边,同时修改路径上每条边的容量
        v=m;
        cout<<"路径: ";
        while(prev[v])
        {
            cout<<prev[v]<<endl;
            G[prev[v]][v]-=mi;
            G[v][prev[v]]+=mi;
            v=prev[v];
        }
        return mi;
    
    }
    int main()
    {
        while(~scanf("%d%d",&n,&m))
        {
            int s,e,c;
            memset(G,0,sizeof(G));
            for(int i=1;i<=n;i++)
            {
                scanf("%d%d%d",&s,&e,&c);
                G[s][e]+=c;
            }
            int maxFlow=0,aug;
            while(aug=Augment()) //每次增广增加的流量
                maxFlow+=aug;
            printf("%d
    ",maxFlow);
        }
    }

    Dinic算法实现:

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    int G[300][300];
    bool vis[300];
    int Layer[300];
    int n,m;
    const int inf=0x3f3f3f3f;
    bool bfs()//用于网络分层
    {
        deque<int>q;
        memset(Layer,0xff,sizeof(Layer));//初始化为-1
        Layer[1]=0;
        q.push_back(1);
        while(!q.empty())
        {
            int v=q.front();
            q.pop_front();
            for(int i=1;i<=m;i++)
            {
                if(G[v][i]>0&&Layer[i]==-1)
                {
                    Layer[i]=Layer[v]+1;
                    if(i==m) //达到汇点
                        return true;
                    else
                        q.push_back(i);
                }
            }
        }
        return false;
    }
    int Dinic()
    {
        deque<int>q;
        int i,nMaxFlow=0;
        while(bfs())//能不能分层
        {
            //用栈写dfs,可以保存增广路径信息
            q.push_back(1);//加入原点
            memset(vis,0,sizeof(vis));
            vis[1]=1;
            while(!q.empty())
            {
                int nd=q.back();
                if(nd==m)//如果是汇点
                {
                    //在栈中找容量最小的边
                    int nMinc=inf;
                    int nMinc_vs; //容量最小边的起点
                    for(i=1;i<q.size();i++)
                    {
                        int vs=q[i-1];
                        int ve=q[i];
                        if(G[vs][ve]>0)
                        {
                            if(nMinc>G[vs][ve])
                            {
                                nMinc=G[vs][ve]; //增广路径上最小的流量
                                nMinc_vs=vs;
                            }
                        }
                    }
                    //增广,改图
                    nMaxFlow+=nMinc;
                    for(i=1;i<q.size();i++)
                    {
                        int vs=q[i-1];
                        int ve=q[i];
                        G[vs][ve]-=nMinc; //修改边容量
                        G[ve][vs]+=nMinc; //添加反向边
                    }
                    //退栈使nMinc_vs成为栈顶,以便继续dfs
                    while(!q.empty()&&q.back()!=nMinc_vs)
                    {
                        vis[q.back()]=0;
                        q.pop_back();
                    }
                }
                else
                {
                    for(i=1;i<=m;i++)
                    {
                        if(G[nd][i]>0&&!vis[i]&&Layer[i]==Layer[nd]+1)
                        {
                            vis[i]=1;
                            q.push_back(i);
                            break;
                        }
                    }
                    if(i>m) //找不到下一个点,回溯
                        q.pop_back();
                }
            }
    
        }
        return nMaxFlow;
    }
    int main()
    {
        while(~scanf("%d%d",&n,&m))
        {
            int s,e,c;
            memset(G,0,sizeof(G));
            for(int i=1;i<=n;i++)
            {
                scanf("%d%d%d",&s,&e,&c);
                G[s][e]+=c;
            }
            cout<<Dinic()<<endl;
        }
    }

    最小费用最大流

    问题描述:在所有最大流集合中,求出费用最小的路径

    最小费用流:在所有流中,代价最小的流

    解题思路:

    1 求从出发点到汇点的最小费用通路u(s,t)

    2  对该通路分配最大可行的流量:f=min(c(i,j)),并让通路上所有边的流量减少f。这时,对于通路上的饱和边,费用应改为正无穷

    3 做该通路的反向边(j,i),令c(j,i)=f, d(j,i)=-d(i,j)

    4 在这样构成的网络中,重复1,2,3.直到找不到从源点到汇点的最小费用通路为止。

    反复使用spfa算法做从源点到汇点的最短路增广操作。

    NOI 志愿者招募

    //OJ上的题目也可以先转化成对偶问题再进行求解
    
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int maxn=1003,maxm=10002*4;
    const int inf=0x7fffffff;
    struct edge{
        edge *next,*op;
        int t,c,v;  //容量,代价
    }ES[maxm],*V[maxn];
    int demond[maxn],sp[maxn],prev[maxn];
    edge *path[maxn];
    int N,M,S,T,EC=-1;
    inline void addedge(int a,int b,int v,int c=inf)
    {
        edge e1={V[a],0,b,c,v},e2={V[b],0,a,0,-v};
        ES[++EC]=e1;V[a]=&ES[EC];
        ES[++EC]=e2;V[b]=&ES[EC];
        V[a]->op=V[b];V[b]->op=V[a];
    }
    struct Queue{
        int Q[maxn],QH,QL,Size;
        bool inq[maxn];
        inline void ins(int v) //插入
        {
            if(++QL>=maxn)
                QL=0;
            Q[QL]=v;
            inq[v]=true;
            Size++;
        }
        inline int pop()    //弹出
        {
            int r=Q[QH];
            inq[r]=false;
            Size--;
            if(++QH>=maxn) //循环
                QH=0;
            return r;
        }
        inline void reset() //重置
        {
            memset(Q,0,sizeof(Q));
            QH=Size=0;
            QL=-1;
        }
    }Q;
    void init()
    {
        int i,a,b,c;
        scanf("%d%d",&N,&M);
        for( i=1;i<=N;i++)
            scanf("%d",&demond[i]);//每天最少的志愿者数量
        for( i=1;i<=M;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            addedge(a,b+1,c); //对变量X添加边
        }
        S=0,T=N+2;
        for(i=1;i<=N+1;i++)
        {
            c=demond[i]-demond[i-1];
            if(c>=0)
                addedge(S,i,0,c); //从源点到顶点连边
            else
                addedge(i,T,0,-c); //从顶点到汇点连边
            if(i>1)
                addedge(i,i-1,0); //对变量Y连边
        }
    }
    //用spfa根据边上权值找一条最短路径,保存这一路径
    bool spfa()
    {
        int u,v;
        for(u=S;u<=T;u++)
            sp[u]=inf;
        Q.reset();
        Q.ins(S);
        sp[S]=0;
        prev[S]=-1;
        while(Q.Size)
        {
            u=Q.pop();
            for(edge *k=V[u];k;k=k->next)
            {
                v=k->t;
                if(k->c>0&&sp[u]+k->v<sp[v])
                {
                    sp[v]=sp[u]+k->v;
                    prev[v]=u;
                    path[v]=k;
                    if(!Q.inq[v])
                        Q.ins(v);
                }
            }
        }
        return sp[T]!=inf;
    }
    int argument()
    {
        int i,delta=inf,flow=0;
        edge *e;
        for(i=T;prev[i]!=-1;i=prev[i]) //路径上的最小流量
        {
            e=path[i];
            if(e->c<delta)
                delta=e->c;
        }
        for(i=T;prev[i]!=-1;i=prev[i])
        {
            e=path[i];
            e->c-=delta;e->op->c+=delta; //容量减少,建立反向边
            flow+=e->v*delta; //计算代价
        }
        return flow;
    }
    int maxcostflow()
    {
        int ans=0;
        while(spfa())
        {
            ans+=argument();//增广操作
        }
        return ans;
    }
    int main()
    {
        init();//建图
        printf("%d
    ",maxcostflow());
        return 0;
    }
  • 相关阅读:
    IntelliJ IDEA常用统一设置2-Inspections检查设置(Linux/Mac/Windows)
    IntelliJ IDEA版本:Ultimate、Community、EAP版本的区别
    IntelliJ IDEA重构技巧收集
    Java泛型中的类型擦除机制简单理解
    阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义
    Java中PO、BO、VO、DTO、POJO、DAO概念及其作用和项目实例图(转)
    Java使用logback记录日志时分级别保存文件
    Java中List,Set和Map详解及其区别和使用场景(转)
    Java中泛型的Class<Object>与Class<?>的区别(转)
    Java中泛型T和Class<T>以及Class<?>的理解(转)
  • 原文地址:https://www.cnblogs.com/flightless/p/12056739.html
Copyright © 2011-2022 走看看