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更新流量。


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