poj 2159||hdu 1533 Going Home

poj 2159||hdu 1533 Going Home

最小费用最大流
或KM算法求二分图最优匹配

KM算法
//KM算法:想在网上找一些证明,可惜都看不懂,还对KM很陌生···
//
//建图,矮人为x部,house为y部,x部的点顶标初始化为该
//点跟y部连边的边权值.y不顶标为0。由于这题要求的是最小值所
//以把边权加负号,KM求得的最优匹配加负号就相当于最小值了,
//求完备匹配的话就进行n次匹配,for过去,每次从迭代变量开始匹配,
//若匹配不成功则对x部和y部顶标进行松弛。Hungary时,标记x部的点,
//要先判断x部顶标加上y部顶标要等于边权值时才标记y部点,
//然后看y部点是否已匹配,跟普通匈牙利一样。
//顶标和大于边权值(不可能小于),用一个松弛数组保存y部点最小的
//松弛量即顶标和减去边权值。若匹配没成功则在松弛数组中找出最小
//的做为松弛量,把刚才hungary时有遍历到的x部点顶标减去松弛量,
//有遍历到的y部点加上松弛量,这里y部点其实是已匹配成功的点,所以
//更新完顶标后已匹配的点对的顶标和还是等于边权值;没遍历到的y部点
//对应的松弛数组的值要减去松弛量,然后继续hungary
//
//
//注意:
//1、先判断x部顶标加上y部顶标要等于边权值时才标记y部点,表示该y部点
//有遍历到
//2、没遍历到的y部点对应的松弛数组的值要记得减去松弛量
//3、KM对x部点逐个遍历进行匈牙利前要对松弛数组slack初始化为INF,
//匹配一个点只能初始化一次,不是每次hungary都要初始化,
//不管hungary有没有成功只初始化一次。遍历下一个点时才可以再初始化
//
//还有就是要先保证完备匹配,即有x1->y1, x1->y2, x2->y1这三条边,权值分别为
//5,1,1 这时x1 先跟y1匹配,然后匹配x2时,先找到y1。而x1找到y2时
//发现顶标和不等于边权值,所以松弛量为lx[x1] + ly[y1] - w[x1][y1]= 5+0-1=4
//然后顶标lx[x1]=1,lx[x1]=1,ly[y1]=0,ly[y2]=4
//再次hungary匹配结果(x1,y2),(x2,y1),虽然权值和比较小,但我们是要先确保
//完备匹配
#define in freopen("in.txt", "r", stdin);
#include <stdio.h>
#include <string.h>

#define INF (1<<30)
#define N 105

struct Point
{
    int x, y;
}man[N], house[N];

int map[N][N], lx[N], ly[N];
int slack[N];   //y部的松弛量
int right[N];   //y部匹配到的x部某个点的下标
bool visx[N], visy[N];

int abs(int num)
{
    return num >= 0 ? num : -num;
}

bool hungary(int x, int n)
{
    visx[x] = true;
    for(int i = 0; i < n; ++i)
    {
        if(visy[i] == true)
            continue;
        int slk = lx[x] + ly[i] - map[x][i];
        if(slk == 0)    //要当x 与 y的顶标和等于 边权值时
        {           //表示这两点可以匹配,
            visy[i] = true;
            if(right[i] == -1 || hungary(right[i], n))
            {
                right[i] = x;
                return true;
            }
        }//当x 与 y的顶标和 大于(不可能小于)边权时,
        else    //就更行y部该点的最小松弛量
            slack[i] = slack[i] > slk ? slk : slack[i];
    }
    return false;
}

int KM(int n)
{
    for(int i = 0; i < n; ++i)  //要找n次增广路,一次匹配一对,总的n对
    {
        for(int j = 0; j < n; ++j)
            slack[j] = INF; //不知道为什么 这里要初始化松弛量
        while(1)
        {
            for(int j = 0; j < n; ++j)
                visx[j] = visy[j] = false;

            if(hungary(i, n) == true)
                break;

            int slk = INF;
            for(int j = 0; j < n; ++j)
                if(visy[j] == false && slk > slack[j])
                    slk = slack[j]; //找到最小松弛量

            for(int j = 0; j < n; ++j)
            {
                if(visx[j] == true) //x部有遍历到的要减去松弛量
                    lx[j] -= slk;
                if(visy[j] == true) //y部有遍历到的要加上松弛量
                    ly[j] += slk;   //注意若之前hungary时顶标和大于边权值的
                else    //y部点是当做没有遍历的,这样该顶标就不会被更改(其实
                    slack[j] -= slk;//就是有匹配成功的y部就是有遍历到的)
            }               //更改后的x部的顶标加上y部的顶标就可能等于边权值了
        }           //而原本匹配的y部也有更改顶标所以加上x不的顶标还是等于边权值
    }               //所以这样就有可能多匹配一对
    int ans = 0;
    for(int i = 0; i < n; ++i)
        ans += map[right[i]][i];
    printf("%d\n", -ans);
}

int main()
{
    int row, col;
    while(scanf("%d%d", &row, &col), row||col)
    {
        int n_man = 0, n_house = 0;
        for(int i = 0; i < row; ++i)
        {
            getchar();
            for(int j = 0; j < col; ++j)
            {
                char ch = getchar();
                if(ch == 'm')
                {
                    man[n_man].x = i;
                    man[n_man++].y = j;
                }
                if(ch == 'H')
                {
                    house[n_house].x = i;
                    house[n_house++].y = j;
                }
            }
        }
        for(int i = 0; i < n_man; ++i)
        {
            right[i] = -1;
            lx[i] = -INF;
            ly[i] = 0;
            for(int j = 0; j < n_house; ++j)
            {   //因为这题要求的是最小值,所以在把边权值变为负的
                //求出最大值输出时加个负号就是答案了
                map[i][j] = -(abs(man[i].x - house[j].x) +
                              abs(man[i].y - house[j].y));

                //把x部的顶标设为该点与y部连边中的最大值
                lx[i] = lx[i] > map[i][j] ? lx[i] : map[i][j];
            }
        }
        KM(n_man);
    }
    return 0;
}

 

最小费用最大流
//最小费用最大流,是费用优先考虑,然后才是最大流
//直接spfa找到sink的最小费用就可以,流量根据容量建反向边就会
//自己调整,挺神奇的,也不知道这样理解对还是错···

#define infile freopen("in.txt", "r", stdin);
#include <stdio.h>
#include <string.h>
#include <queue>

using namespace std;

#define N 20010

struct POINT
{
    int x, y;
}man[N], house[N];

struct EDGE
{
    int from, to, cap, cost, next;
}edge[2*N];   //有反向边

int eid, n;
int head[N], fa[N], dis[N];
bool vis[N];

int abs(int num)
{
    return num > 0 ? num : -num;
}

void add_edge(int from, int to, int cap, int cost)
{
    edge[eid].from = from;
    edge[eid].to = to;
    edge[eid].cap = cap;
    edge[eid].cost = cost;
    edge[eid].next = head[from];
    head[from] = eid++;

    edge[eid].from = to;
    edge[eid].to = from;        //建反向边
    edge[eid].cap = 0;
    edge[eid].cost = -cost;
    edge[eid].next = head[to];
    head[to] = eid++;
}

bool spfa(int now)
{
    memset(vis, false, sizeof(vis));
    memset(dis, -1, sizeof(dis));
    queue<int>que;
    que.push(now);
    vis[now] = true;
    dis[now] = 0;
    while(!que.empty())
    {
        now = que.front();
        que.pop();
        for(int i = head[now]; i != -1; i = edge[i].next)
        {
            int to = edge[i].to;
            if(edge[i].cap > 0 && (dis[to] == -1 || dis[to] - dis[now] > edge[i].cost))
            {
                dis[to] = dis[now] + edge[i].cost;
                fa[to] = i;
                if(vis[to] == false)
                {
                    que.push(to);
                    vis[to] = true;
                }
            }
        }
        vis[now] = false;
    }
    if(dis[20005] == -1)
        return false;
    return true;    //有找到路在这返回true,不是一找到sink就返回
}

void max_flow()
{
    int ans = 0;
    while(spfa(0))
    {
        for(int i = 20005; i != 0; i = edge[fa[i]].from)
        {
            edge[fa[i]].cap -= 1;   //下标从0开始,奇数为逆向边
            edge[fa[i]^1].cap += 1; //所以偶数异或完刚好是奇数
        }
        ans += dis[20005];  //累加最小费用
    }
    printf("%d\n", ans);
}

int main()
{
    infile
    int row, col;
    while(scanf("%d%d", &row, &col), row||col)
    {
        eid = 0;
        memset(head, -1, sizeof(head));
        int n_man = 0, n_house = 0;
        for(int i = 0; i < row; ++i)
        {
            getchar();
            for(int j = 0; j < col; ++j)
            {
                char ch = getchar();
                if(ch == 'm')
                {
                    man[++n_man].x = i;
                    man[n_man].y = j;
                }
                if(ch == 'H')
                {
                    house[++n_house].x = i;
                    house[n_house].y = j;
                }
            }
        }
        n = n_man;
        for(int i = 1; i <= n_man; ++i)
        {
            add_edge(0, i, 1, 0);   //设source为0,source到人
            add_edge(i+n_man, 20005, 1, 0); //设sink为20005,house 到汇点
            for(int j = 1; j <= n_house; ++j)
            {
                add_edge(i, n_man+j, 1,
                abs(man[i].x - house[j].x) + abs(man[i].y - house[j].y));
            }
        }
        max_flow();
    }
    return 0;
}

 

posted @ 2012-08-06 13:51  gabo  阅读(203)  评论(0编辑  收藏  举报