最大流和最小费用流

最大流

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

基本思路:对任一一个可行流,求出其残余网络,在残余网络中,找到一条增广路径,确定该路径的流量(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\n",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\n",maxcostflow());
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/flightless/p/12056739.html