You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##LightOJ 1152 Hiding Gold

一、题目描述

题意:有n*m个方格,有些里面有金子,现在用1*2的骨牌覆盖所有的金子,骨牌可以横着放或者竖着放,骨牌可以叠加。求覆盖所有的金子需要多少骨牌

二、思路

先求出所有的恰好可以用骨牌覆盖的金子,也就是所有两两相邻的金子个数,这些金子两个用一个骨牌覆盖,可以用匈牙利算法来求,相邻的金子连边,把每个金子拆成两个点,求出最大匹配。然后金子数减去两两相邻的金子个数就是落单的金子个数,这些金子每个需要一个骨牌来覆盖,于是求出答案

解释 :由于对每个金子拆点,求出的最大匹配是双倍的,除以2才是真正的最大匹配,不除以2则是匹配的点,那么节点数 - 匹配的点 = 落单的个数。明显落单的金子每个需要一个骨牌,匹配的点两个需要一个骨牌 于是,答案= 节点 - 匹配点 + 匹配点 / 2

三、全新建图+离散化

#include <bits/stdc++.h>
using namespace std;

const int N = 510, M = N * N * 2;
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};

// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// 匈牙利算法
int st[N], match[N];
int dfs(int u) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (st[v]) continue;
        st[v] = 1;
        if (match[v] == -1 || dfs(match[v])) {
            match[v] = u;
            return 1;
        }
    }
    return 0;
}

char g[50][50];
int id[50][50];
int main() {
#ifndef ONLINE_JUDGE
    freopen("LightOJ1152.in", "r", stdin);
#endif
    int T, n, m, cas = 0;
    scanf("%d", &T);
    while (T--) {
        // 初始化链式前向星
        memset(h, -1, sizeof h);
        idx = 0;

        scanf("%d%d", &n, &m);
        // 按char[]数组读入原始地图
        for (int i = 1; i <= n; i++) scanf(" %s", g[i] + 1);

        // 给每个金子编号,相当于离散化
        memset(id, 0, sizeof id);
        int cnt = 0;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                if (g[i][j] == '*') id[i][j] = ++cnt;

        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                if (id[i][j]) {
                    for (int k = 0; k < 4; k++) {
                        int tx = i + dx[k], ty = j + dy[k];
                        if (!id[tx][ty]) continue;
                        if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
                        add(id[i][j], id[tx][ty]); // 对每个金子,和它四个方向上的金子连边,没有就不连
                    }
                }
        int res = 0;
        memset(match, -1, sizeof match);
        for (int i = 1; i <= cnt; i++) {
            memset(st, 0, sizeof st);
            if (dfs(i)) res++;
        }

        /*
         *由于对每个金子拆点求出的最大匹配是双倍的除以2才是真正的最大匹配不除以2则是匹配的点
         *那么节点数 - 匹配的点 = 落单的个数。明显落单的金子每个需要一个骨牌,匹配的点两个需要一个骨牌
         *于是,答案= 节点 - 匹配点 + 匹配点 / 2
         */
        printf("Case %d: %d\n", ++cas, cnt - res / 2);
    }
    return 0;
}

四、按自定规则编号建图

#include <bits/stdc++.h>
using namespace std;

const int N = 510, M = N * N * 2;
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};

// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// 匈牙利算法
int st[N], match[N];
int dfs(int u) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (st[v]) continue;
        st[v] = 1;
        if (match[v] == -1 || dfs(match[v])) {
            match[v] = u;
            return 1;
        }
    }
    return 0;
}

char g[50][50];
int main() {
#ifndef ONLINE_JUDGE
    freopen("LightOJ1152.in", "r", stdin);
#endif
    int T, n, m, cas = 0;
    scanf("%d", &T);
    while (T--) {
        // 初始化链式前向星
        memset(h, -1, sizeof h);
        idx = 0;

        scanf("%d%d", &n, &m);
        // 按char[]数组读入原始地图
        for (int i = 1; i <= n; i++) scanf(" %s", g[i] + 1);

        // 给每个金子重新编号
        int cnt = 0;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                if (g[i][j] == '*') {
                    cnt++;
                    for (int k = 0; k < 4; k++) {
                        int tx = i + dx[k], ty = j + dy[k];
                        if (g[tx][ty] != '*') continue;
                        if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
                        add((i - 1) * m + j, (tx - 1) * m + ty); // 对每个金子,和它四个方向上的金子连边,没有就不连
                    }
                }
        int res = 0;
        memset(match, -1, sizeof match);
        for (int i = 1; i <= n * m; i++) {
            memset(st, 0, sizeof st);
            if (dfs(i)) res++;
        }
        /*
         *由于对每个金子拆点求出的最大匹配是双倍的除以2才是真正的最大匹配不除以2则是匹配的点
         *那么节点数 - 匹配的点 = 落单的个数。明显落单的金子每个需要一个骨牌,匹配的点两个需要一个骨牌
         *于是,答案= 节点 - 匹配点 + 匹配点 / 2
         */
        printf("Case %d: %d\n", ++cas, cnt - res / 2);
    }
    return 0;
}

五、总结

  • 能离散化,最好离散化,可以避免无效点过多,一来可能空间开不够导致WA掉,也可能由于无效点访问造成速度慢。
  • 无向图,所以最大是N*N*2,别问我是怎么知道的,我的一个小时一直在排查问题,结果是数组开小了~