最小费用最大流 POJ2195-Going Home

网络流相关知识参考: http://www.cnblogs.com/luweiseu/archive/2012/07/14/2591573.html

出处:優YoU http://blog.csdn.net/lyy289065406/article/details/6732762

大致题意:

给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。

解题思路:

费用流问题。

构图:

       把man作为一个顶点集合U,house作为另一个顶点集合V,把U中所有点到V中所有点连线,费用cost[u][v]为abs(△x)+abs(△y),反向弧费用cost[v][u]= -cost[u][v],容量cap[u][v]=1,构成一个多源多汇的二分图。

由于每一个多源多汇的网络流都必有一个与之对应的单源单汇的网络流,为了便于解题,由此构造一个超级源s和超级汇t,超级源s与U中所有点相连,费用cost[s][u]=0(这是显然的),容量cap[s][u]=1;V中所有点与超级汇t相连,费用cost[v][t]=0(这是显然的),容量cap[t][v]=1。

至于其他不连通的点,费用与容量均为0。容量为0的边,可以理解为饱和边,不再连通。而上述的所有边之所以容量初始化为1,是因为每间house只允许入住1个man。而与超级源(汇)相连的边的费用之所以为0,是为了现在所构造的单源单汇网络流最终所求的最小费用等于原来的多源多汇网络流的最小费用。

求解:

接下来的解题方法有关“最小费用最大流”,请未知的同学先去看看相关文献。

       其实题目所求的最小费用,就是最短距离。用spfa算法求上述二分图G的最短路径,该最短路径就是图G的所有增广链中费用最小的一条。比较该增广链上所有边的容量,最小的容量就是“可分配的最大流MaxFlow”。

       再者就是利用MaxFlow对增广链上的各条边的容量进行调整,正向弧容量减去MaxFlow,反向弧容量加上MaxFlow。然后该条增广链上各条边的费用分别乘以MaxFlow之和,就是第一个man到达合适的house所花费的最小费用。而图G经过调整,变成图G1。

       针对图G1再次使用spfa算法,找到第二条增广链.....重复上述算法,直到无法找到增广链为止,则得到的费用和就是所求。

       此种解题方法是先计算最小费用,然后再在保证费用最小的情况下,增大流量,最终达到求解目的。理论上,对于有n个man的图,可以找到n条增广链,即重复迭代spfa算法n次。若超过n次,则说明该图存在负权环,无解。但本题并无输出“无解”的要求,故可认为测试数据中不存在此类数据,因此在循环spfa算法时,不必计算次数,用while()足矣。

最后要注意的是:

       Discuss上很多人说用了栈空间定义的数组,提交会RE或TLE,数组开大之后才能AC,因此便怪罪于测试数据有误。

       其实不然。真正的原因是,题目所提及的N与M纯粹是地图的行数和列数,与man或house的个数无关,而我看了他们的代码,有部分同学却误以为N就是man的个数,导致出现RE。

       因此在此强调一下,题目的N不等于man的个数,建议大家先对地图的man个数进行数数,得到man的个数,然后再开堆空间(new函数);而非要开栈空间的同学也未尝不可,由于N与M的值均<=100,且man与house的个数一致,则认为man的个数上限为100*100/2=5000。

//Memory Time
//612K   63MS 

#include<iostream>
#include<memory.h>
#include<cmath>
#include<queue>
using namespace std;

struct coordinate
{
    int x,y;
};

class solve
{
public:
    solve(int row,int col):R(row),C(col)
    {
        MinCost=0;
        n=0;

        Input();
        StructureBinaryMap();

        pre=new int[2*n+2];
        memset(pre,0,sizeof(int)*(2*n+2));
        dist=new int[2*n+2];
        vist=new bool[2*n+2];

        while(spfa())
            AddMaxFlow();
    }
    ~solve()
    {
        cout<<MinCost<<endl;

        delete[] m;
        delete[] H;
        delete[] dist;
        delete[] vist;

        for(int i=0;i<R;i++)
            delete[] InMap[i];

        for(int j=0;j<2*n+2;j++)
        {
            delete[] cost[j];
            delete[] cap[j];
        }
    }

    int inf() const{return 0x7FFFFFFF;}
    int min(int a,int b) {return a<b?a:b;}

    void Input(void);
    void StructureBinaryMap(void);   //构造二分图
    bool spfa(void);                 //搜索从超级源到超级汇的最短路(即最小费用)的增广链
    void AddMaxFlow(void);             //增广链调整(曾流),计算最小费用

protected:
    int R,C;        //地图尺寸R*C ,
    int n;          //the number of man or Houses
    int s,t;        //s:超级源编号,t:超级汇编号
    int MinCost;    //最小花费

    char** InMap;   //输入的地图
    coordinate* m;    //记录所有man的坐标
    coordinate* H;    //记录所有House的坐标
    int* pre;        //最小费用流路径上,结点i的前驱结点为pre[i]

    int** cost;        //两点间费用,其中超级源为0,1~n为man,n+1~2n为house,2n+1为超级汇
    int** cap;        //两点间容量,其中超级源为0,1~n为man,n+1~2n为house,2n+1为超级汇
    int* dist;        //超级源到各点的最短距离
    bool* vist;        //标记各点是否在队列
};

void solve::Input(void)
{
    int i,j;

    InMap=new char*[R];
    for(i=0;i<R;i++)
    {
        InMap[i]=new char[C];
        for(j=0;j<C;j++)
        {
            cin>>InMap[i][j];

            if(InMap[i][j]==‘m‘)    //检查‘m‘
                n++;                //得到man or house的数量
        }
    }
    return;
}

void solve::StructureBinaryMap(void)
{
    int i,j;
    int pm=0,pH=0;

    m=new coordinate[n+1];
    H=new coordinate[n+1];    

    /*记录各个man与house的坐标*/

    for(i=0;i<R;i++)
        for(j=0;j<C;j++)
            if(InMap[i][j]==‘m‘)
            {
                m[++pm].x=i;
                m[pm].y=j;
            }
            else if(InMap[i][j]==‘H‘)
            {
                H[++pH].x=i;
                H[pH].y=j;
            }

    /*建立存储空间*/

    cost=new int*[2*n+2];
    cap=new int*[2*n+2];
    for(i=0;i<2*n+2;i++)
    {
        cost[i]=new int[2*n+2];
        cap[i]=new int[2*n+2];

        memset(cost[i],0,sizeof(int)*(2*n+2));  //所有路径费用默认为0
        memset(cap[i],0,sizeof(int)*(2*n+2));   //所有路径容量默认为0(饱和,不连通)
    }

    /*初始化超级源s到各个man的容量*/

    s=0;
    for(i=1;i<=n;i++)
        cap[s][i]=1;    //容量默认为1

    /*初始化各个man到house的距离(费用)及容量*/

    for(i=1;i<=n;i++)
        for(j=n+1;j<=2*n;j++)
        {
            cost[i][j]=abs(m[i].x-H[j-n].x)+abs(m[i].y-H[j-n].y);   //man到house的费用为abs(△x)+abs(△y)
            cost[j][i]=-cost[i][j];        //注意顺便构造负权边
            cap[i][j]=1;
        }

    /*初始化各个house到超级汇t的容量*/

    t=2*n+1;
    for(j=n+1;j<t;j++)
        cap[j][t]=1;    //容量默认为1

    return;
}

bool solve::spfa(void)
{
    dist[s]=0;
    for(int i=1;i<2*n+2;i++)   //注意这里不能用memset(),memset是对字节为单位进行赋值
        dist[i]=inf();    //若非置0,memset对char、bool以外的类型都会赋一个与期望完全不同的数值

    memset(vist,false,sizeof(bool)*(2*n+2));
    vist[s]=true;

    queue<int>q;
    q.push(s);

    while(!q.empty())
    {
        int u=q.front();    //获取队头元素
        for(int v=0;v<=t;v++)
        {
            if(cap[u][v] && dist[v]>dist[u]+cost[u][v])  //u->v容量未饱和,且能够松弛
            {
                dist[v]=dist[u]+cost[u][v];
                pre[v]=u;        //记录u的前驱结点

                if(!vist[v])
                {
                    q.push(v);
                    vist[v]=true;
                }
            }
        }
        q.pop();     //队头元素出队
        vist[u]=false;
    }

    if(dist[t]==inf())  //dist[t]没有被调整,说明已不存在增广链
        return false;

    return true;    //找到一条当前费用和最小的增广链
}

void solve::AddMaxFlow(void)
{
    int MaxFlow=inf();  //可分配最大流
    int i;

    for(i=t;i!=s;i=pre[i])  //可分配最大流 为增广链上的最小容量边的容量
        MaxFlow=min(MaxFlow,cap[pre[i]][i]);

    for(i=t;i!=s;i=pre[i])    //增广链上流量调整(增流)
    {
        cap[pre[i]][i]-=MaxFlow;    //正向弧容量减去可分配最大流
        cap[i][pre[i]]+=MaxFlow;    //反向弧容量加上可分配最大流
        MinCost+=cost[pre[i]][i]*MaxFlow;  //最小费用=单位费用*流量
    }

    return;
}

int main(void)
{
    int row,col;
    while(cin>>row>>col && (row+col))
        solve poj2195(row,col);

    return 0;
}
时间: 2024-08-29 10:23:12

最小费用最大流 POJ2195-Going Home的相关文章

POJ-2195 Going Home(最小费用最大流模板)

题目链接:POJ-2195 Going Home 题意 给出$N$行$M$列的网格,'m'表示人,'H'表示房子,且'm'和'H'的数量相同,一个人只能进一间房子,一间房子也只能给一个人进去,人可走上下左右四个方向,现在要让所有人进入房子,求最短路径和. 思路 这是一个二分图带权最小匹配问题,可直接用最小费用最大流求解. 源点向人各连一条容量为1,费用为0的边,每个人向每间房子连容量为1,费用为距离的边,每间房子向汇点连容量为1,费用为0的边,然后跑最小费用最大流即可. 代码实现 #includ

【BZOJ3876】【Ahoi2014】支线剧情 有下界的最小费用最大流

#include <stdio.h> int main() { puts("转载请注明出处谢谢"); puts("http://blog.csdn.net/vmurder/article/details/43025375"); } [BZOJ2324]营救皮卡丘 这道题也是一道有下界的最小费用最大流. 我的题解地址:http://blog.csdn.net/vmurder/article/details/41378979 这道题其实就是模板题. 我的处理

POJ 3686.The Windy&#39;s 最小费用最大流

The Windy's Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 5477   Accepted: 2285 Description The Windy's is a world famous toy factory that owns M top-class workshop to make toys. This year the manager receives N orders for toys. The ma

P3381 【模板】最小费用最大流

P3381 [模板]最小费用最大流 题目描述 如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行包含四个正整数ui.vi.wi.fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi. 输出格式: 一行,包含两个整数,依次为最大流量和在最大流量情况下的

C++之路进阶——最小费用最大流(支线剧情)

F.A.Qs Home Discuss ProblemSet Status Ranklist Contest ModifyUser  hyxzc Logout 捐赠本站 Notice:由于本OJ建立在Linux平台下,而许多题的数据在Windows下制作,请注意输入.输出语句及数据类型及范围,避免无谓的RE出现. 3876: [Ahoi2014]支线剧情 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 542  Solved: 332[Submit

hdu 4494 Teamwork 最小费用最大流

Teamwork Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=4494 Description Some locations in city A has been destroyed in the fierce battle. So the government decides to send some workers to repair these location

POJ - 2195 Going Home(最小费用最大流)

1.N*M的矩阵中,有k个人和k个房子,每个人分别进入一个房子中,求所有人移动的最小距离. 2.人看成源点,房子看成汇点,求最小费用最大流. 建图-- 人指向房子,容量为1,费用为人到房子的曼哈顿距离. 建立超级源点和超级汇点:超级源点指向人,容量为1,费用为0:超级汇点指向房子,容量为1,费用为0. 求超级源点到超级汇点的最小费用最大流即可. ps:容量为什么都设为1?---有待研究.. 3. 1.Bellman-Ford: #include<iostream> #include<st

hdu 1853 Cyclic Tour 最小费用最大流

题意:一个有向图,现在问将图中的每一个点都划分到一个环中的最少代价(边权和). 思路:拆点,建二分图,跑最小费用最大流即可.若最大流为n,则说明是最大匹配为n,所有点都参与,每个点的入度和出度又是1,所以就是环. /********************************************************* file name: hdu1853.cpp author : kereo create time: 2015年02月16日 星期一 17时38分51秒 *******

最小费用最大流

Farm Tour http://poj.org/problem?id=2135 建图再说吧 1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cmath> 5 #include<map> 6 #include<stack> 7 #include<queue> 8 #include<vector> 9 #include&l