Going Home - poj 2195(最小费用流 | 二分匹配)

题目大意:在一个网格里面有n个小男人和n个房子,现在想让每个小男人都有一个房子住,不过每个人移动一下都需要花费¥1,现在求出来最小的总花费。ps:可以认为网格的每个点都是很大的广场并且容纳所有的人,人可以走在有房子的点但是不进入房子。

分析:人-房子,很完美的带全都最小值匹配啊,人到一个房子的花费就是他们之间的曼哈顿距离,用这些距离构造一个二分图,然后用KM算法求出来最小费。下面是KM算法
***********************************************************************************************************************************
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
#include<math.h>
using namespace std;

const int MAXN = 107;
const int oo = 1e9+7;

struct point{int x, y;}man[MAXN], house[MAXN];
char G[MAXN][MAXN];
int w[MAXN][MAXN], slack[MAXN], Nx, Ny;
int dx[MAXN], dy[MAXN], Ly[MAXN];
bool vx[MAXN], vy[MAXN];

void InIt()
{
    Nx = Ny = 0;

    for(int i=1; i<MAXN; i++)
    {
        dx[i] = -oo;
        dy[i] = 0;
    }

}
bool Find(int i)
{
    vx[i] = true;
    for(int j=1; j<=Ny; j++)
    {
        if(!vy[j] && w[i][j] == dx[i]+dy[j])
        {
            vy[j] = true;
            if(!Ly[j] || Find(Ly[j]))
            {
                Ly[j] = i;
                return true;
            }
        }
        else if(!vy[j])
            slack[j] = min(slack[j], dx[i]+dy[j]-w[i][j]);
    }

    return false;
}
int KM()
{
    int i, j;

    memset(Ly, 0sizeof(Ly));

    for(i=1; i<=Nx; i++)
    {
        for(j=1; j<=Ny; j++)
            slack[j] = oo;
        while(true)
        {
            memset(vx, falsesizeof(vx));
            memset(vy, falsesizeof(vy));

            if(Find(i) == true)
                break;

            int d = oo;

            for(j=1; j<=Ny; j++)
            {
                if(!vy[j] && d > slack[j])
                    d = slack[j];
            }

            for(j=1; j<=Nx; j++)
            {
                if(vx[j])
                    dx[j] -= d;
            }
            for(j=1; j<=Ny; j++)
            {
                if(vy[j])
                    dy[j] += d;
                else
                    slack[j] -= d;
            }
        }
    }

    int sum = 0;

    for(i=1; i<=Ny; i++)
        sum += w[Ly[i]][i];

    return -sum;
}

int main()
{
    int M, N;

    while(scanf("%d%d", &M, &N), M+N)
    {
        int i, j;

        InIt();

        for(i=0; i<M; i++)
            scanf("%s", G[i]);

        for(i=0; i<M; i++)
        for(j=0; j<N; j++)
        {
            if(G[i][j] == 'm')
            {
                Nx++;
                man[Nx].x = i;
                man[Nx].y = j;
            }
            if(G[i][j] == 'H')
            {
                Ny++;
                house[Ny].x = i;
                house[Ny].y = j;
            }
        }

        for(i=1; i<=Nx; i++)
        for(j=1; j<=Ny; j++)
        {
            w[i][j] = fabs(man[i].x-house[j].x) + fabs(man[i].y-house[j].y);
            w[i][j] = -w[i][j];
            dx[i] = max(dx[i], w[i][j]);
        }

        printf("%d\n", KM());
    }

    return 0;
}
View Code

研究了一下网络网络流里面的最小费用最大流,听着名字比较高大上吧,看了半天终于恍然大悟,其实还是最大流。但是为什么又叫最小费用呢?所谓的最小费用并不是从源点到汇点的最小费用,而是再保证最大流的前提下的最小费用,我们求最大流的的办法就是不断进行路径增广,而怎么增广路径就比较随意了,可以用深搜或者广搜,不管怎么样只要找到可以进行增广的路就行,如果在这个前提下我们找增广路的时候用最短路的办法去找,那么岂不是找的每条路都是花费最小的,而且最后无路时候就是最小花费了(同时也求出来了最大流),有了这个认识就可以写最消费最大流了。

下面是用spfa(可以处理负权边)实现的。
***********************************************************************************************************************************
#include<stdio.h>
#include<string.h>
#include<queue>
#include<stack>
#include<algorithm>
#include<math.h>
using namespace std;

const int MAXN = 407;
const int oo = 1e9+7;

struct point{int x, y;}man[MAXN], house[MAXN];
struct Graph{int flow, cost;}G[MAXN][MAXN];
int NX, NY, start, End;///男人和房子的数目,源点和汇点

bool spfa(int pre[])
{
    stack<int> sta;
    int instack[MAXN]={0}, dist[MAXN];

    for(int i=1; i<=End; i++)
        dist[i] = oo;

    dist[start] = 0;
    sta.push(start);

    while(sta.size())
    {
        int u = sta.top();sta.pop();
        instack[u] = false;

        for(int i=1; i<=End; i++)
        {
            if(G[u][i].flow &&  dist[i] > dist[u]+G[u][i].cost)
            {
                dist[i] = dist[u] + G[u][i].cost;
                pre[i] = u;

                if(instack[i] == false)
                {
                    sta.push(i);
                    instack[i] = true;
                }
            }
        }
    }

    return dist[End] != oo;
}
int MinCost()
{
    int i, pre[MAXN], cost=0;

    while(spfa(pre) == true)
    {///如果有增广路
        int MinFlow = oo;

        for(i=End; i != start; i=pre[i])
            MinFlow = min(MinFlow, G[pre[i]][i].flow);
        for(i=End; i != start; i=pre[i])
        {///逆向访问这条增广路上的每条边
            int k = pre[i];
            G[k][i].flow -= MinFlow;
            G[i][k].flow += MinFlow;
            cost += G[k][i].cost;
        }
    }

    return cost;
}

int main()
{
    int M, N;

    while(scanf("%d%d", &M, &N), M+N)
    {
        int i, j;char s[MAXN][MAXN];

        memset(G, 0sizeof(G));
        NX = NY = 0;

        for(i=0; i<M; i++)
            scanf("%s", s[i]);

        for(i=0; i<M; i++)
        for(j=0; j<N; j++)
        {
            if(s[i][j] == 'm')
            {
                NX++;
                man[NX].x = i;
                man[NX].y = j;
            }
            if(s[i][j] == 'H')
            {
                NY++;
                house[NY].x = i;
                house[NY].y = j;
            }
        }

        for(i=1; i<=NX; i++)
        for(j=1; j<=NY; j++)
        {///房子的编号从NX~NX+NY
            G[i][NX+j].flow = 1;
            G[i][NX+j].cost = fabs(man[i].x-house[j].x)+fabs(man[i].y-house[j].y);
            G[NX+j][i].cost = -G[i][NX+j].cost;
        }

        start = NX+NY+1, End = start+1;

        for(i=1; i<=NX; i++)
        {///把源点与人连接
            G[start][i].flow = 1;
            G[start][i].cost = 0;
        }
        for(i=1; i<=NY; i++)
        {///把房子和汇点连接
            G[NX+i][End].flow = 1;
            G[NX+i][End].cost = 0;
        }

        printf("%d\n", MinCost());
    }

    return 0;
}
View Code
posted @ 2015-08-08 15:45  无忧望月  阅读(222)  评论(1编辑  收藏  举报
levels of contents