【二分图最佳匹配】移动棋子

在一个n*n的棋盘上有n枚棋子。每次可以把一枚棋子往上、下、左、右方向之一移动一格,最后排成一行、一列或者主、副对角线上(因此一共有2n+2条可能的目标状态),要求移动次数最小。
棋盘上有一些位置是障碍,棋子在任何时候都不能经过。棋子的初始位置保证不在障碍物上。任两枚棋子不能在同时到达	同一个格子。


【输入文件】
    输入文件move.in第一行包含两个整数n, m,表示棋子的个数(它也是棋盘的边长)和障碍的个数。以下n行,每行两个整数(x, y),表示第i个棋子的坐标(1<=x, y<=n),以下m行,每行给出一个障碍物的坐标。假设这n+m个坐标两两不重合。
【输出文件】
    输出文件仅包含一个整数,表示最小移动步数。如果无解,输出-1。

【样例】
move.in
5 1
1 2
2 4
3 4
5 1
5 3
1 1
move.out
6
【限制】50%的数据满足:2<=n<=15,m=0100%的数据满足:2<=n<=50, 0<=m<=100

 
此题考察KM算法的应用。首先以所有棋子为起点,分别求一次单源最短路(由于图非常稀疏,所以用广搜接就行了。然后,枚举终点(按行,列,以及两条对角线枚举),分别做一次最小匹配(用KM算法),最后在所有的匹配值中取一个最小值即可。Accode: 

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <cstring>

const char fi[] = "move.in", fo[] = "move.out";
const int maxN = 60, SIZE = 0xffff;
const int MAX = 0x3f3f3f3f, MIN = ~MAX;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};
struct Node
{
    int x, y; Node() {}
    Node(int x, int y): x(x), y(y) {}
} q[SIZE + 1], chess[maxN], target[maxN];
bool _chess[maxN], _target[maxN];
bool mp[maxN][maxN], vis[maxN][maxN];
int lx[maxN], ly[maxN], dist[maxN][maxN][maxN];
int Link[maxN], n, m, f, r, ans = MAX;

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

void init()
{
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
    n = getint(); m = getint();
    for (int i = 0; i < n; ++i)
    {
        int x = getint(), y = getint();
        chess[i] = Node(x - 1, y - 1);
    }
    memset(mp, 1, sizeof mp);
    for (int i = 0; i < m; ++i)
    {
        int u = getint(), v = getint();
        mp[u - 1][v - 1] = 0;
    }
    return;
}

void Bfs(int S)
{
    memset(vis, 0, sizeof vis);
    dist[S][chess[S].x][chess[S].y] = 0;
    vis[chess[S].x][chess[S].y] = 1;
    q[(r = f = 0)++] = chess[S];
    while (f < r)
    {
        Node Now = q[f++];
        int x = Now.x, y = Now.y;
        for (int j = 0; j < 4; ++j)
        {
            int u = x + dx[j], v = y + dy[j];
            if (u < 0 || u >= n || v < 0 || v >= n)
                continue;
            if (mp[u][v] && !vis[u][v])
            {
                vis[u][v] = 1;
                dist[S][u][v] = dist[S][x][y] + 1;
                q[r++] = Node(u, v);
            }
        }
    }
    return;
}

bool Find(int i)
{
    _chess[i] = 1;
    for (int j = 0; j < n; ++j)
    if (!_target[j] && lx[i] + ly[j] ==
        dist[i][target[j].x][target[j].y])
    {
        _target[j] = 1;
        if (Link[j] == -1 || Find(Link[j]))
        {Link[j] = i; return 1;}
    }
    return 0;
}

int KM()
{
    for (int i = 0; i < n; ++i)
    {
        lx[i] = MAX; ly[i] = 0; Link[i] = -1;
        for (int j = 0; j < n; ++j)
            lx[i] = std::min(lx[i],
                dist[i][target[j].x][target[j].y]);
    }
    for (int k = 0; k < n; ++k)
	//这里循环变量一定不能用i。
    while (1)
    {
        memset(_chess, 0, sizeof _chess);
        memset(_target, 0, sizeof _target);
        if (Find(k)) break; int Max = MIN;
        for (int i = 0; i < n; ++i) if (_chess[i])
        for (int j = 0; j < n; ++j) if (!_target[j])
            Max = std::max(Max, lx[i] + ly[j] -
                           dist[i][target[j].x]
                           [target[j].y]);
        for (int i = 0; i < n; ++i)
        {
            if (_chess[i]) lx[i] -= Max;
            if (_target[i]) ly[i] += Max;
        }
    } //注意求最小匹配和最大匹配略有不同。
    int ans = 0;
    for (int i = 0; i < n; ++i)
        ans += dist[Link[i]][target[i].x][target[i].y];
    return ans;
}

void work()
{
    memset(dist, 0x3f, sizeof dist);
    for (int i = 0; i < n; ++i) Bfs(i);
	//一定要以每个棋子作为起点求最短路,否则浪费。
    for (int i = 0; i < n; ++i)
    {
        bool ok = 1;
        for (int j = 0; j < n; ++j)
            if (!mp[i][j]) {ok = 0; break;}
        if (!ok) continue;
        for (int j = 0; j < n; ++j)
            target[j] = Node(i, j);
        ans = std::min(ans, KM());
    }
    for (int j = 0; j < n; ++j)
    {
        bool ok = 1;
        for (int i = 0; i < n; ++i)
            if (!mp[i][j]) {ok = 0; break;}
        if (!ok) continue;
        for (int i = 0; i < n; ++i)
            target[i] = Node(i, j);
        ans = std::min(ans, KM());
    }
    bool ok = 1;
    for (int i = 0; i < n; ++i)
        if (!mp[i][i]) {ok = 0; break;}
    if (ok)
    {
        for (int i = 0; i < n; ++i)
            target[i] = Node(i, i);
        ans = std::min(ans, KM());
    }
    ok = 1;
    for (int i = 0; i < n; ++i)
        if (!mp[i][n - i - 1]) {ok = 0; break;}
    if (ok)
    {
        for (int i = 0; i < n; ++i)
            target[i] = Node(i, n - i - 1);
        ans = std::min(ans, KM());
    }
    printf("%d\n", ans < MAX ? ans : -1);
    return;
}

int main()
{
    init();
    work();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值