P6008 [USACO20JAN] Cave Paintings P 题解

P6008 [USACO20JAN] Cave Paintings P 题解

简化题意

我们有一个 \(n\)\(m\) 列的网格,每格内会有一个符号。若是 #,则代表这个格子是堵塞的;若是 .,则代表这个格子是空的。

现在,我们在其中任意数量的空格子中倒水。

已知:假设我们在空格子 \((x, y)\) 中倒水,而 \((x, y)\) 与空格子 \((i, j)\) 可以通过一条仅经过空格子与带水格子的路径相连,且对于此路径中所有格子的高度都不大于 \((x, y)\)高度(即 \(n - x + 1\)),则空格子 \((i, j)\) 中必须有水,即只要在空格子 \((x, y)\) 中加水,空格子 \((i, j)\) 无论是否加水都会有水(整条路径中也都会有水,通俗一下解释就是水往低处流,但无法往更高处流)。(连通器原理)

我们希望求出:网格有多少种不同的情况(空白也算)

解题思路

暴力 BFS/DFS 明显是不可行的。我们考虑其他的算法。

这里,我们先来看一下部分样例,来找一下灵感。

#.#...#.#

这里,我们发现,空白格子被分成了三块:.... 以及另一个 .
而假如我们只研究这行,会有多少种情况呢?
很明显,答案数量就是 $\mathrm C_3^0 + \mathrm C_3^1 + \mathrm C_3^2 + \mathrm C_3^3 = 1 + 3 + 3 + 1 = 8 = 2 ^ 3 $。这是互不干扰的情况,但是要是出现联通的情况怎么办呢?

联通与合并,遂考虑并查集

比较容易看出来,有这些特点:

  • 相邻空白格可以合并。
  • 两个空白格上下相邻的情况,上面的格子可以合并下面的格子。

于是,我们可以把这个网格看成一个树形结构;再通过样例来理解一下。

#...#...#
#.#...#.#

这样,所有空白格子就成了一个连通块。我们把下面三个连通块看成三个子节点,上面的就看作父节点。这里我们开始考虑 DP 求解。父节点继承子节点的情况(类似树形 DP),根据乘法原理,转移方程即为 \(dp_i = \prod dp_j + 1\),其中,\(j\) 均为 \(i\) 的子节点。

这样最终的答案是什么呢?因为经过了并查集的操作,所以对于每个连通块,它的根节点所对的 \(dp\) 值,即为这个连通块的情况数。根据乘法原理,最后的答案就是所有连通块根节点 \(dp\) 值的乘积

总结一下,这题难就难在首先,要意识到暴力不可行;其次,要想到连通块与合并,与并查集有关;最后,要抽象出树形结构,推出 DP 状态转移方程。还是很有意思的题目。不要像我模拟赛的时候打个暴力就走

代码部分

#include <bits/stdc++.h>
#define calc(x, y) ((x - 1) * m + y) //用来计算每个格子的唯一编号 
const int MOD = 1e9 + 7;
using namespace std;

int n, m;
int a[1010][1010], fa[1000010];
long long dp[1000010];
int find(int x)//查询根节点(并查集操作) 
{
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}

int main()
{
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
		for(int j = 1;j <= m;j ++)
		{
			char c;
			cin >> c;
			a[i][j] = (c == '.' ? 1 : 0);//转化为数字,方便操作 
			fa[calc(i, j)] = calc(i, j);//并查集初始化 
			dp[calc(i, j)] = 1;//dp 值初始化 
		}
	
	for(int i = n;i >= 1;i --)//从下到上操作(因为水不能往高处流) 
	{
		for(int j = 3;j < m;j ++)//合并相邻的连通块 
			if(a[i][j] && a[i][j - 1])//都是空白格子 
				fa[find(calc(i, j))] = find(calc(i, j - 1));//并查集合并操作 
		
		for(int j = 2;j < m;j ++)//子节点找父节点 
			if(a[i][j] && a[i + 1][j])//都是空白格子 
			{
				int fx = find(calc(i, j)), fy = find(calc(i + 1, j));//各自查询根节点 
				if(fx == fy)
					continue;
				fa[fy] = fx;//并查集合并操作 
				dp[fx] = dp[fx] * dp[fy] % MOD;//DP 值转移至根节点 
			}
		
		for(int j = 2;j < m;j ++)
			if(a[i][j] && find(calc(i, j)) == calc(i, j))//作为根节点,要加上全都选择的情况 
				dp[calc(i, j)] ++;
	}
	
	long long ans = 1;
	
	for(int i = 1;i <= n;i ++)
		for(int j = 1;j <= m;j ++)
			if(a[i][j] && find(calc(i, j)) == calc(i, j))//作为连通块的根节点 
				ans = ans * dp[calc(i, j)] % MOD;
	
	
	cout << ans << endl;
	
	return 0;
}

posted on 2026-09-05 22:13  Leo_29  阅读(5)  评论(0)    收藏  举报

导航