LightOJ 1152 Hiding Gold

发布时间 2023-08-02 15:12:25作者: 糖豆爸爸

\(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\),别问我是怎么知道的,我的一个小时一直在排查问题,结果是数组开小了~