最大流和最小費用流


最大流

在圖網絡中,找到從源點到匯點的最大流量

基本思路:對任一一個可行流,求出其殘余網絡,在殘余網絡中,找到一條增廣路徑,確定該路徑的流量(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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM