Optimal Milking-最大流/FordFulkerson/Dinic

Optimal Milking
Source:POJ-2112

Description
FJ has moved his K (1 <= K <= 30) milking machines out into the cow pastures among the C (1 <= C <= 200) cows. A set of paths of various lengths runs among the cows and the milking machines. The milking machine locations are named by ID numbers 1..K; the cow locations are named by ID numbers K+1..K+C.
Each milking point can "process" at most M (1 <= M <= 15) cows each day.
Write a program to find an assignment for each cow to some milking machine so that the distance the furthest-walking cow travels is minimized (and, of course, the milking machines are not overutilized). At least one legal assignment is possible for all input data sets. Cows can traverse several paths on the way to their milking machine.

Input
* Line 1: A single line with three space-separated integers: K, C, and M.
* Lines 2.. ...: Each of these K+C lines of K+C space-separated integers describes the distances between pairs of various entities. The input forms a symmetric matrix. Line 2 tells the distances from milking machine 1 to each of the other entities; line 3 tells the distances from machine 2 to each of the other entities, and so on. Distances of entities directly connected by a path are positive integers no larger than 200. Entities not directly connected by a path have a distance of 0. The distance from an entity to itself (i.e., all numbers on the diagonal) is also given as 0. To keep the input lines of reasonable length, when K+C > 15, a row is broken into successive lines of 15 numbers and a potentially shorter line to finish up a row. Each new row begins on its own line.

Output
A single line with a single integer that is the minimum possible total distance for the furthest walking cow.


Sample Input1
2 3 2
0 3 2 1 1
3 0 3 2 0
2 3 0 1 0
1 2 1 0 2
1 0 0 2 0

Sample Output1
2


Sample Input2
3 3 1
0 0 0 5 0 6
0 0 0 1 5 0
0 0 0 0 0 5
5 1 0 0 2 0
0 5 0 2 0 0
6 0 5 0 0 0

Sample Output2
5

源代碼一(Floyed+二分+Ford-Fulkerson):
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

queue <int> Q;
//採用常變量定義INF和MAX,比起#define錯誤率低(考慮括號匹配不正確的情況,因爲移位運算符優先級略低)
const int INF = 1<<29;    //Floyd有兩條路徑相加的情況,如果INF定義爲1<<30的話肯能會溢出
const int MAX = 300;
int K , C , M  , S , T , l , r , m , pre[MAX] , mark[MAX];
int  map[MAX][MAX] , gragh[MAX][MAX];
//l/m/r是二分的左中右的值,S/T爲網絡流源點和匯點
//map是輸入的地圖,以及Floyed以後形成的路徑圖
//gragh是每一次進行Ford_Fulkerson算法求最大流的暫存圖

int  Ford_Fulkerson( int axis );
void Floyed( void );
int  bfs( void );
int  upDate( void );
int  binarySearch( void );
void formNewGragh( int axis );

int main( ){
    int i , j;

    while( cin>>K>>C>>M ){
        for( i=1 ; i<=K+C ; i++ ){
            for( j=1 ; j<=K+C ; j++ ){
                cin>>map[i][j];
                if( !map[i][j] )    //按照題意對圖進行處理,0的話表示到達不了
                    map[i][j] = INF;
            }
        }
        
        Floyed( );    //Floyed求機器到牛(即牛到機器(雙向圖))的最短距離
        cout<<binarySearch()<<endl;    //二分查找最長路的最小值.最大的最小\最小的最大可使用二分算法
    }

    return 0;
}

void Floyed( void ){
    int k , i , j;

    l = INF;
    r = -INF;

    for( k=1 ; k<=K+C ; k++ )    //按照Floyed算法的描述,K必須在最外層
        for( i=1 ; i<=K+C ; i++ )
            for( j=1 ; j<=K+C ; j++ )
                if( map[i][k]+map[k][j]<map[i][j] ){
                    map[i][j] = map[i][k] + map[k][j];
                }

    for( i=1 ; i<=K ; i++ )    //找出l\r的界限,代碼的l,r表示閉區間[l,r]
        for( j=K+1 ; j<=K+C ; j++ ){
            if( map[i][j]>r )
                r = map[i][j];
            if( map[i][j]<l )
                l = map[i][j];
        }
}

int binarySearch( void ){
    int temp;

    while( l <= r ){    //如果沒有等號的話不正確,因爲r=m-1,如果當前m最優,那麼r錯過了m的賦值
        m = ( l + r ) / 2;
        if( Ford_Fulkerson( m )==C ){
            temp = m;
            r = m - 1;
        }
        else
            l = m + 1;
    }

    return temp;    //記錄最優解
}

int  Ford_Fulkerson( int axis ){    //axis爲軸的值
    int sum;

    sum = 0;
    formNewGragh( axis );    //形成新的網絡流圖

    while( bfs( ) ){    //尋找增廣路徑
        sum += upDate( );    //補充流量
    }

    return sum;
}

void formNewGragh( int axis ){
    int i , j;

    S = 0;    //源點標號
    T = K + C + 1;    //匯點標號
    memset( gragh , 0 , sizeof( gragh ) );    //初始化gragh圖

    for( i=1 ; i<=K ; i++ )
        for( j=K+1 ; j<=K+C ; j++ )
            if( map[i][j]<=axis )
                gragh[i][j] = 1;

    for( i=1 ; i<=K ; i++ )    //補充源點
        gragh[S][i] = M;

    for( i=K+1 ; i<=K+C ; i++ )    //補充匯點
        gragh[i][T] = 1;
}

int  bfs( void ){
    int u , v;

    memset( mark , 0 , sizeof( mark ) );    //每次需要memset清空
    while( !Q.empty( ) )
        Q.pop( );

    Q.push( S );
    mark[S] = 1;
    pre[S] = -1;

    while( !Q.empty( ) ){
        u = Q.front( );
        Q.pop( );

        if( u==T )
            return 1;

        for( v=0 ; v<=T ; v++ ){
            if( !mark[v] && gragh[u][v] ){
                Q.push( v );    //壓入隊列
                pre[v] = u;        //記錄前驅,以便更新
                mark[v] = 1;    //標記
            }
        }
    }

    return 0;
}

int  upDate( void ){
    int u , v , min;

    v = T;
    min = INF;
    
    while( pre[v]!=-1 ){        //尋找最短可憎流量
        u = pre[v];
        if( gragh[u][v]<min )
            min = gragh[u][v];
        v = u;
    }

    v = T;
    while( pre[v]!=-1 ){        //更新流量
        u = pre[v];
        gragh[u][v] -= min;    //正向減去
        gragh[v][u] += min;    //逆向加上
        v = u;
    }

    return min;
}



源代碼二(Floyed+二分+Dinic):
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

queue <int> Q;
//採用常變量定義INF和MAX,比起#define錯誤率低(考慮括號匹配不正確的情況,因爲移位運算符優先級略低)
const int INF = 1<<29;    //Floyd有兩條路徑相加的情況,如果INF定義爲1<<30的話肯能會溢出
const int MAX = 300;
int K , C , M  , S , T , l , r , m , mark[MAX] , level[MAX];
int  map[MAX][MAX] , gragh[MAX][MAX];
//l/m/r是二分的左中右的值,S/T爲網絡流源點和匯點
//map是輸入的地圖,以及Floyed以後形成的路徑圖
//gragh是每一次進行Ford_Fulkerson算法求最大流的暫存圖

int  Dinic( int axis );
void Floyed( void );
int  bfs( void );
int  dfs( int u , int flow );
int  binarySearch( void );
void formNewGragh( int axis );
int  min( int a , int b ){ return a <= b ? a : b; }

int main( ){
    int i , j;

    while( cin>>K>>C>>M ){
        for( i=1 ; i<=K+C ; i++ ){
            for( j=1 ; j<=K+C ; j++ ){
                cin>>map[i][j];
                if( !map[i][j] )    //按照題意對圖進行處理,0的話表示到達不了
                    map[i][j] = INF;
            }
        }
        
        Floyed( );    //Floyed求機器到牛(即牛到機器(雙向圖))的最短距離
        cout<<binarySearch()<<endl;    //二分查找最長路的最小值.最大的最小\最小的最大可使用二分算法
    }

    return 0;
}

void Floyed( void ){
    int k , i , j;

    l = INF;
    r = -INF;

    for( k=1 ; k<=K+C ; k++ )    //按照Floyed算法的描述,K必須在最外層
        for( i=1 ; i<=K+C ; i++ )
            for( j=1 ; j<=K+C ; j++ )
                if( map[i][k]+map[k][j]<map[i][j] ){
                    map[i][j] = map[i][k] + map[k][j];
                }

    for( i=1 ; i<=K ; i++ )    //找出l\r的界限,代碼的l,r表示閉區間[l,r]
        for( j=K+1 ; j<=K+C ; j++ ){
            if( map[i][j]>r )
                r = map[i][j];
            if( map[i][j]<l )
                l = map[i][j];
        }
}

int binarySearch( void ){
    int temp;

    while( l <= r ){    //如果沒有等號的話不正確,因爲r=m-1,如果當前m最優,那麼r錯過了m的賦值
        m = ( l + r ) / 2;
        if( Dinic( m )==C ){
            temp = m;
            r = m - 1;
        }
        else
            l = m + 1;
    }

    return temp;    //記錄最優解
}

int  Dinic( int axis ){    //axis爲軸的值
    int sum = 0;

    formNewGragh( axis );

    while( bfs( ) ){
        sum += dfs( S , INF );
    }

    return sum;
}

void formNewGragh( int axis ){
    int i , j;

    S = 0;    //源點標號
    T = K + C + 1;    //匯點標號
    memset( gragh , 0 , sizeof( gragh ) );    //初始化gragh圖

    for( i=1 ; i<=K ; i++ )
        for( j=K+1 ; j<=K+C ; j++ )
            if( map[i][j]<=axis )
                gragh[i][j] = 1;

    for( i=1 ; i<=K ; i++ )    //補充源點
        gragh[S][i] = M;

    for( i=K+1 ; i<=K+C ; i++ )    //補充匯點
        gragh[i][T] = 1;
}

int  bfs( void ){
    int u , v;

    memset( mark , 0 , sizeof( mark ) );    //每次需要memset清空
    memset( level , 0 , sizeof( level ) );
    while( !Q.empty( ) )
        Q.pop( );

    Q.push( S );
    mark[S] = 1;
    level[S] = 0;    //源點層次爲0

    while( !Q.empty( ) ){
        u = Q.front( );
        Q.pop( );

        if( u==T )
            return 1;

        for( v=0 ; v<=T ; v++ ){
            if( !mark[v] && gragh[u][v] ){
                Q.push( v );    //壓入隊列
                mark[v] = 1;    //標記
                level[v] = level[u] + 1;    //層次記錄
            }
        }
    }

    return 0;
}

int  dfs( int u , int flow ){
    int v , flowSum , sum;

    if( u==T )    //遞歸終止條件
        return flow;

    for( sum=0 , v=S ; v<=T ; v++ ){
        if( level[v]==level[u]+1 && gragh[u][v] ){    //bfs進行分層處理,速度明顯加快
            flowSum = dfs( v , min( gragh[u][v] , flow ) );    //flowSum是從該點開始,流經匯點的流量
            gragh[u][v] -= flowSum;
            gragh[v][u] += flowSum;
            flow -= flowSum;    //下一個結點能流量爲flow-flowSum
            sum += flowSum;    //flowSum累計該點流出的總流量
        }
    }

    return sum;    //回溯,返回該結點流出的總流量
}


代碼分析:題目採用了兩種方法求解,大體的思路在代碼註釋中了,Dinic算法是FordFulkerson的加強版(一次性找多條增廣路),需要利用bfs求層,dfs更新流量。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章