|
|
##[$LightOJ$ $1152$ $Hiding$ $Gold$](https://lightoj.com/problem/hiding-gold)
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
题意:有$n*m$个方格,有些里面有金子,现在用$1*2$的骨牌覆盖所有的金子,骨牌可以横着放或者竖着放,骨牌可以叠加。求覆盖所有的金子需要多少骨牌
|
|
|
|
|
|
|
|
|
### 二、思路
|
|
|
先求出所有的恰好可以用骨牌覆盖的金子,也就是所有两两相邻的金子个数,这些金子两个用一个骨牌覆盖,可以用匈牙利算法来求,相邻的金子连边,把每个金子拆成两个点,求出最大匹配。然后金子数减去两两相邻的金子个数就是落单的金子个数,这些金子每个需要一个骨牌来覆盖,于是求出答案
|
|
|
|
|
|
> **解释** :由于对每个金子拆点,求出的最大匹配是双倍的,除以$2$才是真正的最大匹配,不除以$2$则是匹配的点,那么节点数 - 匹配的点 = 落单的个数。明显落单的金子每个需要一个骨牌,匹配的点两个需要一个骨牌
|
|
|
于是,答案= 节点 - 匹配点 + 匹配点 / $2$
|
|
|
|
|
|
### 三、全新建图+离散化
|
|
|
```cpp {.line-numbers}
|
|
|
#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;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 四、按自定规则编号建图
|
|
|
```cpp {.line-numbers}
|
|
|
#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$,别问我是怎么知道的,我的一个小时一直在排查问题,结果是数组开小了~ |