Optimal Milking-最大流/FordFulkerson/Dinic

本文介绍了一种解决最优挤奶路径问题的算法,通过Floyd算法预处理路径,结合二分查找与最大流算法(Ford-Fulkerson及Dinic算法),以找到使最远行走距离最小化的挤奶机分配方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值