lightoj 1061 KM 二分图匹配

/*
    KM  二分图匹配    MCMF   DP  搜索   好题
    题意:在8*8棋盘上,给出8个皇后的初始位置,要求移动这8个皇后使得皇后之间互不攻击。皇后移动一步可以沿直线或对角线移动任意个格子,但不>能跨过一个原本有皇后的格,即会被有皇后的格挡住。皇后的攻击方式跟移动方式一样的。    问最小移动总步数。




    思路:neko最初给我看的时候我第一反应是状压DP,但感觉很复杂,搞不动。
          他说只要能证明一个结论,这道题就很简单了。要证:存在一种最终布局,使得最优移动过程中任何有皇后不互挡,也就是说我们可以不用考虑“不能跨皇后”这个限制,直接“穿越”做。
          我画了个4*4的图,然后幸运地得出了证明:假设现在在i点的皇后要到k点去(那k点当然是最终布局的皇后点啦,不然到那里去干嘛),中间有个j(j点有个皇后)隔住了。 那么k与j就在一条直线上,也就是说 j 不是最终皇后点,那么 j 就应该要挪到她相应的点去,等她挪完后就不挡住i 到达 k 点了。


          证了上述结论就好办了,用二分图带权匹配KM算法或者最大流最小费用流MCMF都可以做,费用为移动步数。
          我是用KM 做的。先预处理出8*8上所有合理布局,一共92种。然后对于每种合理布局,拿来跟初始布局搞二分图带权匹配。易知这个二分图是完
全二分图(表述不太准确,就是X部Y部两两有边)。


          这道题是归类在DP上的 orz... 在vj上有人用搜索+dp做... 
*/

//lightoj 1061

#include <limits.h>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define     N       8
#define     ABS(x)      ((x) > 0 ? (x) : (-(x)))
//bool flag = false;  //for debug
int l[3][N*2], X[N], map[100][N], a[N][2];              //map[i][x] 存第i种合理布局的x行皇后应该放的列数
int edge[N][N], LX[N], LY[N], link[N], slack[N];
bool visx[N], visy[N];
int Top = 0;
void label(int row, int col)
{
    l[0][col] ^= 1;
    l[1][row+col] ^= 1;
    l[2][row-col+N] ^= 1;
}
bool check(int row, int col)
{
    return l[0][col] && l[1][row+col] && l[2][row-col+N];
}
void init(int row)
{
    if(row == N) {
        for(int i = 0; i < N; i++) map[Top][i] = X[i];
        Top++;
        return ;
    }
    for(int i = 0; i < N; i++) {
        if(check(row, i)) {
            X[row] = i;
            label(row, i);
            init(row+1);
            label(row, i);
        }
    }
}
inline char rdc() { scanf(" "); return getchar(); }
void read_board()
{
    int k = 0;
    for(int i = 0; i < N; i++)
        for(int j = 0; j < N; j++)
            if(rdc() == 'q') a[k][0] = i, a[k][1] = j, k++;
}
int dis(int x1, int y1, int x2, int y2)
{
    if(x1 == x2 && y1 == y2) return 0;
    if(x1 == x2 || y1 == y2) return 1;
    if(x1 + y1 == x2 + y2 || (x1 - y1) == (x2 - y2)) return 1;      //妹的,本来写的是对的,看了neko的,然后改错了=_=
    return 2;
}
void build(int * map) 
{
    for(int i = 0; i < N; i++) {
        int x = a[i][0], y = a[i][1];
        for(int j = 0; j < N; j++) {
            edge[i][j] = -dis(x, y, j, map[j]);
        }
    }
}
bool find(int u)
{
    visx[u] = true;
    for(int v = 0; v < N; v++) if(!visy[v]) {
        int rest = LX[u] + LY[v] - edge[u][v];
        if(rest == 0) {
            visy[v] = true;
            if(link[v] == -1 || find(link[v])) {
                link[v] = u;
                return true;
            }
        } else slack[v] = min(slack[v], rest);
    }
    return false;
}
int KM()
{
    memset(LX, 128, sizeof(LX));
    memset(LY,   0, sizeof(LY));
    memset(link,-1, sizeof(link));
    for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) LX[i] = max(LX[i], edge[i][j]);
    for(int start = 0; start < N; start ++) {
        for(int i = 0; i < N; i++) slack[i] = (INT_MAX);
        while(true) {
            memset(visx, false, sizeof(visx));
            memset(visy, false, sizeof(visy));
            if(find(start)) break;
            else {
                int d = (INT_MAX);
                for(int i = 0; i < N; i++) if(!visy[i]) d = min(d, slack[i]);
                //if(d == (INT_MAX)) return -1;
                for(int i = 0; i < N; i++) if(visx[i]) LX[i] -= d;
                for(int i = 0; i < N; i++)
                    if(visy[i]) LY[i] += d;
                    else slack[i] -= d;
            }
        }
    }
    int sum = 0;
    for(int i = 0; i < N; i++) sum += edge[link[i]][i];
    return -sum;
}
int main()
{
    int cases, Cas = 0;
    for(int i = 0; i < 3; i++) for(int j = 0; j < 2*N; j++) l[i][j] = 1;
    init(0);
    scanf("%d", &cases);
    while(cases--) {
        read_board();
        int ans = 17;
        for(int i = 0; i < Top; i++) {                  //枚举每一种最终布局
            build(map[i]);
            //if(KM() == 6 && ans != 6) {
            //    for(int j = 0; j < N; j++) printf("(%d,%d)  ", a[j][0]+1,a[j][1]+1);
            //    printf("\n");
            //    for(int j = 0; j < N; j++) printf("(%d,%d)  ", j+1, 1+map[i][j]);
            //    printf("\n");
            //}
            ans = min(ans, KM());
        }
        printf("Case %d: %d\n", ++Cas, ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值