[HDU1533] Going Home(二分图匹配 | KM算法)

传送门:Going Home

题意为,在一块 n*m 的棋盘上,散落着数量相等的人与房子,每个人都可以得到且只能得到一间房子,所花费的代价是其移动到该房子的花费(人可以花费 1 旅费向四个方向种的一个移动一个单位),求所有人与房子匹配成功的最小总花费。

本题只要把人与房子看成是一个带权二分图,利用 KM 算法进行匹配即可。通过 |x_{i}-x_{j}|+|y_{i}-y_{j}| 将每个人到每间房子的花费计算出来,作为边的权值。因为题目要求最小花费,所以需要先将权值取相反数,然后跑 KM,将求得的结果取相反数,就能得到最小花费了。

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e2+2;
struct Node
{
    int x, y;
    Node() {}
    Node(int x, int y): x(x), y(y) {}
};
int n, m;
int nx, ny;
int g[maxn][maxn];
int linker[maxn], lx[maxn], ly[maxn]; //y点中的匹配状态,xy的杠杆值
int slack[maxn]; //新加边的最小差值
bool visx[maxn], visy[maxn];
vector<Node> house, man;

void read()
{
    house.clear();
    man.clear();
    char c;
    for(int i = 0; i < n; ++i)
        for(int j = 0; j < m; ++j)
        {
            g[i][j] = -INF;
            cin >> c;
            if(c == 'H')
                house.push_back(Node(i, j));
            else if(c == 'm')
                man.push_back(Node(i, j));
        }
}

bool dfs(int x)
{
    visx[x] = 1;
    for(int i = 0; i < ny; ++i)
    {
        if(visy[i]) //每一轮匹配 右图每个点只访问一次
            continue;
        int t = lx[x] + ly[i] - g[x][i]; //差值
        if(!t)
        {
            visy[i] = 1;
            if(linker[i] == -1 || dfs(linker[i])) //找到一个未匹配的点,形成增广路
            {
                linker[i] = x;
                return 1;
            }
        }
        else
            slack[i] = min(slack[i], t); //更新最小差值
    }
    return 0;
}

int KM()
{
    memset(linker, -1, sizeof(linker)); //无匹配
    memset(ly, 0, sizeof(ly)); //右图各点期望值为0

    for(int i = 0; i < nx; ++i)
    {
        lx[i] = -INF;
        for(int j = 0; j < ny; ++j)
            lx[i] = max(lx[i], g[i][j]); //lx设置为连接的边权的最大值,即期望的最大值
    }

    for(int i = 0; i < nx; ++i)
    {
        memset(slack, INF, sizeof(slack));

        while(1)
        {
            memset(visx, 0, sizeof(visx));
            memset(visy, 0, sizeof(visy));

            if(dfs(i)) //形成了增广路,匹配成功
                break;

            //否则,新加边使得能够匹配
            int d = INF; //计算d值,d为当前加上一条边,左图需要减少的最小的期望值
            for(int j = 0; j < ny; ++j)
                if(!visy[j])
                    d = min(d, slack[j]); //求得最小值
            for(int j = 0; j < nx; ++j)
                if(visx[j]) //在访问过的点中求
                    lx[j] -= d; //左图减去最小的期望差值
            for(int j = 0; j < ny; ++j)
            {
                if(visy[j]) //在访问过的点中求
                    ly[j] += d; //右图加上最小的期望差值
                else
                    slack[j] -= d; //左图期望降低了,那么差值就变小了
            }
        }
    }
    int ret = 0;
    for(int i = 0; i < ny; ++i)
        if(~linker[i]) //有匹配
            ret += g[linker[i]][i]; //加上权值
    return ret;
}

void solve()
{
    nx = man.size();
    ny = house.size();
    for(int i = 0; i < nx; ++i)
        for(int j = 0; j < ny; ++j)
        {
            Node mt = man[i], ht = house[j];
            g[i][j] = -(abs(mt.y-ht.y) + abs(mt.x-ht.x));
        }
    cout << -KM() << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    while(cin >> n >> m && n && m)
    {
        read();
        solve();
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/HNUCSEE_LJK/article/details/100125191
今日推荐