LightOJ 1152 Hiding Gold
一、题目描述
题意:有个方格,有些里面有金子,现在用的骨牌覆盖所有的金子,骨牌可以横着放或者竖着放,骨牌可以叠加。求覆盖所有的金子需要多少骨牌
二、思路
先求出所有的恰好可以用骨牌覆盖的金子,也就是所有两两相邻的金子个数,这些金子两个用一个骨牌覆盖,可以用匈牙利算法来求,相邻的金子连边,把每个金子拆成两个点,求出最大匹配。然后金子数减去两两相邻的金子个数就是落单的金子个数,这些金子每个需要一个骨牌来覆盖,于是求出答案
解释 :由于对每个金子拆点,求出的最大匹配是双倍的,除以才是真正的最大匹配,不除以则是匹配的点,那么节点数 - 匹配的点 = 落单的个数。明显落单的金子每个需要一个骨牌,匹配的点两个需要一个骨牌
于是,答案= 节点 - 匹配点 + 匹配点 /
三、全新建图+离散化
#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;
}
五、总结
- 能离散化,最好离散化,可以避免无效点过多,一来可能空间开不够导致掉,也可能由于无效点访问造成速度慢。
- 无向图,所以最大是,别问我是怎么知道的,我的一个小时一直在排查问题,结果是数组开小了~
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个适用于 .NET 的开源整洁架构项目模板
· API 风格选对了,文档写好了,项目就成功了一半!
· 【开源】C#上位机必备高效数据转换助手
· .NET 9.0 使用 Vulkan API 编写跨平台图形应用
· MyBatis中的 10 个宝藏技巧!
2021-08-02 P1364 医院设置 题解
2021-08-02 P5076 【深基16.例7】普通二叉树(简化版)题解
2021-08-02 二叉树的三种遍历方式
2021-08-02 P1030 [NOIP2001 普及组] 求先序排列题解
2021-08-02 P1827 [USACO3.4]美国血统 American Heritage 题解
2018-08-02 mysql备份与还原
2017-08-02 PostgreSQL9.6.3的REDIS测试