1. 程式人生 > >最小費用最大流詳解

最小費用最大流詳解

【HDU1533】【POJ2195】

轉自:http://jarily.com/archive.html

/*************************************************** 
演算法引入: 
任何容量網路的最大流流量是唯一且確定的,但是它的最大流f並不是唯一的; 
既然最大流f不唯一,因此,如果每條弧上不僅有容量限制,還有費用r; 
即每條弧上有一個單位費用的引數,那麼在保證最大流的前提下; 
還存在一個選擇費用最小的最大流問題,即為最小費用最大流問題; 
 
演算法思想: 
尋找最大流的方法是從某個可行流出發,找到關於這個流的一條增廣路P; 
沿著P調整f,對新的可行流又試圖尋找關於它的增廣路,迴圈直至不存在增廣路為止; 
要求最小費用最大流: 
如果f是流量為f1的可行流中費用最小者,而p是關於f的所有增廣路中費用最小的增廣路; 
那麼沿著p去調整f,得到可行流_f,就是流量為f1的所有可行流中的費用最小者; 
這樣當f是最大流時,它也就是所要求的最小費用最大流了; 
 
演算法內容: 
在尋找關於f的最小費用增廣路的過程中; 
需要構造一個關於f的伴隨網路W(f); 
把在原網路中尋找關於f的最小費用增廣路轉換為在伴隨網路W(f)中尋找從Vs到Vt的最短路問題; 
 
其中伴隨網路W(f)構造為: 
頂點為原網路中的頂點; 
原網路中的每條弧<u,v>變成兩個方向相反的弧<u,v>和<v,u>; 
在W(f)中每條弧<u,v>的權值為: 
if(f(u,v)<c(u,v)) 
    W(u,v)=r(u,v); 
else if(f(u,v)==c(u,v)) 
    W(u,v)=無窮大(可省略); 
if(f(u,v)>0) 
    W(v,u)=-r(u,v); 
else if(f(u,v)==0) 
    W(v,u)=無窮大(可省略); 
 
演算法流程: 
①開始取f(0)={0}; 
②一般若在第k-1步得到的最小費用流為f(k-1),則構造伴隨網路W(f(k-1)); 
③在W(f(k-1))中尋找從Vs到Vt的最短路,若不存在則轉⑤,存在轉④; 
④在原網路G中得到相應的增廣路P,在P上對f(k-1)進行調整;調整後新的可行流為f(k),轉②; 
⑤f(k-1)為最小費用最大流,演算法完畢; 
 
演算法測試: 
HDU1533,ZOJ2404,POJ2195(Going Home); 
題意: 
在一個網路地圖上,有n個小人和n棟房子; 
在每個單位時間內,每個人可以往水平方向或垂直方向移動一步,走到相鄰的方格中; 
對於每個小人,走一步需支付一美元,直到他走入房子裡,且每棟房子只能容納一個人; 
求讓n個小人移動到n個不同的房子,求需要支付的最小費用; 
****************************************************/  
  
#include<iostream>  
#include<cstring>  
#include<cstdlib>  
#include<cstdio>  
#include<climits>  
#include<algorithm>  
#include<queue>  
using namespace std;  
  
int n,m;  
const int N=250;  
const int M=10000;  
const int MAX=0xffffff;  
char coord[N][N];//座標集  
int pre[M];//儲存前驅頂點  
int dist[M];//儲存到源點s的距離  
  
int inq[M];//每個頂點是否在佇列中的標誌  
int min_c_f;//記錄增廣路徑中的殘留容量  
int vertex;//頂點數  
int sum;//儲存最小費用  
  
struct element  
{  
    int c;//容量  
    int f;//流  
    int c_f;//殘留容量  
    int v;//價值  
} G[N][N];  
  
struct man//記錄小矮人的座標  
{  
    int x,y;  
} man[N];  
struct house//記錄房子的座標  
{  
    int x,y;  
} house[N];  
  
void init()  
{  
    sum=0;  
    int mcase,hcase;//記錄有多少個小矮人和房子  
    mcase=hcase=0;  
    for(int i=1; i<=m; i++)  
    {  
        for(int j=1; j<=n; j++)  
        {  
            cin>>coord[i][j];  
            if(coord[i][j]=='m')//記錄小矮人的座標  
            {  
                mcase++;  
                man[mcase].x=i;  
                man[mcase].y=j;  
            }  
            if(coord[i][j]=='H')//記錄房子的座標  
            {  
                hcase++;  
                house[hcase].x=i;  
                house[hcase].y=j;  
            }  
        }  
    }  
  
    vertex=mcase+hcase+1;//加入超源點0和超匯點,注意要+1,即抽象成網路流的結構  
    for(int u=0; u<=vertex; u++)//初始流為0,所以不用重構W(f);  
    {  
        for(int v=0; v<=vertex; v++)  
        {  
            G[u][v].c=G[v][u].c=0;  
            G[u][v].c_f=G[v][u].c_f=0;  
            G[u][v].f=G[v][u].f=0;  
            G[u][v].v=G[v][u].v=MAX;  
        }  
    }  
  
    for(int i=1; i<=mcase; i++)  
    {  
        G[0][i].v=0;//從超源點到各個小矮人之間的權值取為0  
        G[0][i].c=G[0][i].c_f=1;//從超源點到各個小矮人之間的容量取為1  
        for(int j=1; j<=hcase; j++)  
        {  
            int w=abs(house[j].x-man[i].x)+abs(house[j].y-man[i].y);//計算小矮人到每一個房子之間的距離  
            G[i][mcase+j].v=w;//將距離賦給對應的權值,注意第二個下標,即表示房子的下標為mcase+j~!!  
            G[i][mcase+j].c=1;//容量取為1  
            G[i][mcase+j].c_f=G[i][mcase+j].c;  
            G[mcase+j][vertex].v=0;//將從各個房子到超匯點之間的權值取為0,注意房子的下標為mcase+j  
            G[mcase+j][vertex].c=G[mcase+j][vertex].c_f=1;//將從各個房子到超匯點之間的容量取為0,注意房子的下標為mcase+j  
        }  
    }  
}  
  
void SPFA(int s)//求最短路徑的SPFA演算法  
{  
    queue<int> Q;  
    int u;  
    for(int i=0; i<=vertex; i++)//初始化  
    {  
        dist[i]=MAX;  
        pre[i]=-1;  
        inq[i]=0;  
    }  
    dist[s]=0;  
    Q.push(s);  
    inq[s] = 1;  
    while(!Q.empty())  
    {  
        u=Q.front();  
        Q.pop();  
        inq[u]=0;  
        for(int i=0; i<=vertex; i++)//更新u的鄰接點的dist[], pre[], inq[]  
        {  
            int v=i;  
            if(G[u][v].c_f==0)     // 表示(u,v)沒有邊  
                continue;  
            if(G[u][v].v==MAX)  
                G[u][v].v=-G[v][u].v;  
            if(dist[v]>dist[u]+G[u][v].v)//鬆弛操作  
            {  
                dist[v]=dist[u]+G[u][v].v;  
                pre[v]=u;  
                if(inq[v]==0)  
                {  
                    Q.push(v);  
                    inq[v]=1;  
                }  
            }  
        }  
    }  
}  
  
void ford_fulkerson(int s,int t)  
{  
    SPFA(s);  
    while(pre[t]!=-1)//pre為-1表示沒有找到從s到t的增廣路徑  
    {  
        //cout<<dist[t]<<"^_^"<<endl;  
        sum+=dist[t];//將這一條最短路徑的值加進sum  
        min_c_f=MAX;  
        int u=pre[t], v=t;//計算增廣路徑上的殘留容量  
        while(u!=-1)  
        {  
            if(min_c_f > G[u][v].c_f)  
                min_c_f=G[u][v].c_f;  
            v=u;  
            u=pre[v];  
        }  
        u=pre[t], v=t;  
        while(u!=-1)  
        {  
            G[u][v].f+=min_c_f; //修改流  
            G[v][u].f=-G[u][v].f;  
            G[u][v].c_f=G[u][v].c-G[u][v].f; //修改殘留容量  
            G[v][u].c_f=G[v][u].c-G[v][u].f;  
            v=u;  
            u=pre[v];  
        }  
        SPFA(s);  
    }  
}  
  
int main()  
{  
    //freopen("C:\\Users\\Administrator\\Desktop\\kd.txt","r",stdin);  
    while(cin>>m>>n,m||n)  
    {  
        init();  
        ford_fulkerson(0,vertex);//計算從超源點0到超匯點vertex之間的最小費用最大流  
        cout<<sum<<endl;  
    }  
    return 0;  
}