zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    dockerfile 镜像 指定虚拟机的内存
    springBoot整合MongoDB(动态查询)
    ubuntu 使用sudo apt-get update命令的时候会报http://archive.ubuntukylin.com:10006/ubuntukylin/dists/xenial/InRelease 无法连接
    在Ubuntu下进行MongoDB安装步骤
    艺术和代码的结合 turtle + python 的结合
    MySQL CPU性能定位
    送书丨《架构解密:从分布式到微服务》
    区块链在天猫国际商品溯源中的应用
    nginx gzip
    别被忽悠了!阿里内部人士:我们正悄悄地拆掉中台,你还在建?
  • 原文地址:https://www.cnblogs.com/gabo/p/2625021.html
Copyright © 2011-2022 走看看