【题解】P6008 - Cave Paintings P
前言
某次模拟赛的 \(D\) 题。这道题……说实话,我赛时看到的时候已经只剩一个小时了。果断放弃去对拍,前三题真的太不稳了。不过 \(AC\) 后再回来看,其实赛时如果给我三个小时,我也未必能够做出正解,大概只能拿几档暴力的部分分。这道题赛后也是看了题解才能 \(AC\) ,不得不感慨大佬就是能把大家都能看出来的 并查集 变成具体做法的神奇存在。
这道题对我而言其实有点超出认知了。这种做法除非我发散思维的时候运气好,否则根本无法通过已知的任何经验来推断。这也恰恰说明了做题的 经验和手感 是多么重要。
题目大意
给定一个 \(n \times m\) 的初始矩阵,该矩阵包含 .
和 #
两种字符,且该矩阵的边框一定是 #
。现在需要在若干个 .
上画水,使得矩阵符合物理原理。也就是:假设方格 \(a\) 画水,如果方格 \(a\) 可以经过若干满足以下条件的方格到达方格 \(b\):
-
高度不超过 \(a\)
-
字符不是
#
-
与方格 \(a\) 四联通
那么方格 \(b\) 中也应该画水。试求有多少种不同的矩阵满足该条件,结果对 \(10^9 + 7\) 取模。
\(1 \leq n, m \leq 1000\)
解题思路
这道题是计数问题,但是涉及到 连通性,所以排除 \(dp\) 做法。我们考虑如何使用并查集来维护方案总数。
这里直接给出做法。我们从倒数第二行开始枚举。对于高度为 \(2\) 的连通块,可以选择给整个连通块倒满水或是不倒。方案总数为 \(2\)。当我们从第 \(k\) 行走到第 \(k - 1\) 行时,第 \(k\) 行及以下的子矩阵中的连通块可能会通过第 \(k\) 行连通,再次合并。例如下表中位置分别为 \((3, 2)\) 和 \((3, 4)\) 的两个连通块,在由第 \(3\) 行和第 \(4\) 行组成的子矩阵中并不连通。但是在第 \(2\) 行及以下组成的子矩阵中,它们是连通的。
#####
#...#
#.#.#
#####
因此,我们可以枚举第 \(k - 1\) 行的每一个字符和第 \(k\) 行与其连通的字符,然后将它们所在的并查集合并。设连通块 \(i\) 的方案总数为 \(f_i\),则连通块 \(i\) 和连通块 \(j\) 合并后,新连通块的方案总数为 在第 \(k - 1\) 行画水的方案总数 \(+\) 不在第 \(k - 1\) 画水的方案总数。因为在 \(k - 1\) 行画水都会导致整个连通块被灌水,而不在第 \(k - 1\) 行画水就可以在原本的两个小连通块内 自由划水(这么好?!),根据加乘原理,方案总数为 \(f_i \times f_j + 1\) 。
根据乘法原理,最后的答案是每一个连通块的方案总数的乘积,也就是 \(\prod\limits_{i} f_i\),其中 \(i\) 满足 \(fa_i = i\)。
参考代码
#include <cstdio>
using namespace std;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
int n, m;
int id[maxn][maxn], fa[maxn * maxn];
int dx[3] = {1, 0, 0}, dy[3] = {0, -1, 1};
long long dp[maxn * maxn];
bool vis[maxn * maxn];
char s[maxn][maxn];
int get(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
fa[y] = x;
dp[x] = (dp[x] * dp[y]) % mod;
}
}
int main() {
int tx, ty;
long long ans = 1;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", s[i] + 1);
for (int j = 1; j <= m; j++) {
id[i][j] = (i - 1) * m + j;
fa[id[i][j]] = id[i][j];
dp[id[i][j]] = 1;
}
}
for (int i = n - 1; i >= 2; i--) {
for (int j = 2; j <= m - 1; j++) {
if (s[i][j] == '#') {
continue;
}
for (int k = 0; k < 3; k++) {
tx = i + dx[k], ty = j + dy[k];
if (s[tx][ty] != '#') {
merge(id[i][j], id[tx][ty]);
}
}
}
for (int j = 2; j <= m - 1; j++) {
if (s[i][j] == '#') {
continue;
}
if (!vis[get(id[i][j])]) {
vis[get(id[i][j])] = true;
dp[get(id[i][j])] = (dp[get(id[i][j])] + 1) % mod;
}
}
for (int j = 2; j <= m - 1; j++) {
if (s[i][j] == '#') {
continue;
}
vis[get(id[i][j])] = false;
}
}
for (int i = 2; i <= n - 1; i++) {
for (int j = 2; j <= m - 1; j++) {
if (s[i][j] == '#') {
continue;
}
if (fa[id[i][j]] == id[i][j]) {
ans = (ans * dp[id[i][j]]) % mod;
}
}
}
printf("%lld\n", ans);
return 0;
}