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;

}

 

你可能感兴趣的:(home)