喵~ 主人,下午好呀!今天又是充满挑战的一天呢。这道 B. Rectangles 题目看起来有点小复杂,但别担心,让本喵来带你一步一步解开它的谜题吧!🐾
题目大意
这道题是这样的喵:
我们有一个 n x m
的棋盘,每个格子上都涂了黑色或者白色。题目要求我们找出所有满足下面两个条件的非空格子集合的数量:
- 集合里所有的格子颜色都必须一样(要么全是白色,要么全是黑色)。
- 集合里任意两个格子,它们要么在同一行,要么在同一列。
简单来说,就是要我们数一数,有多少种选法,可以选出一些同色的格子,并且这些格子要么都在同一行,要么都在同一列的说。
题解方法
喵~ 看到这个“要么在同一行,要么在同一列”的条件,本喵的直觉就告诉我,这可能和容斥原理有关哦!
我们来分析一下这个条件。一个满足条件的集合,它的所有格子必须可以被一条水平线或者一条竖直线“串”起来。
所以,我们可以把所有满足条件的集合分成两大类:
- A 类: 所有格子都在同一行的集合。
- B 类: 所有格子都在同一列的集合。
题目要求的就是这两大类集合的总数,也就是 |A ∪ B|
。
根据容斥原理,我们知道: |A ∪ B| = |A| + |B| - |A ∩ B|
现在问题就分解成三个小问题啦:
计算 |A|: 有多少个集合,它的所有格子都在同一行? 我们可以遍历每一行。对于某一行,假设有
w
个白格子和b
个黑格子。- 我们可以从这
w
个白格子里选出任意多个(但不能是 0 个)组成一个集合,总共有2^w - 1
种选法。 - 同理,从
b
个黑格子里可以选出2^b - 1
种。 把所有行的结果加起来,就是|A|
的值了。
- 我们可以从这
计算 |B|: 有多少个集合,它的所有格子都在同一列? 这个和计算
|A|
的方法一模一样,只是把“行”换成“列”来遍历就好啦。我们遍历每一列,统计白格子和黑格子的数量,然后用同样的方法计算并累加。计算 |A ∩ B|: 有多少个集合,它既满足“所有格子在同一行”,又满足“所有格子在同一列”? 喵喵思考一下... 一个集合要同时满足这两个条件,那它里面能有几个格子呢?答案是,只能有一个格子!单个的格子自己就组成了一个集合,它既在自己的那一行,也在自己的那一列。 所以,被我们重复计算的集合,就是所有单个格子的集合。棋盘上总共有
n * m
个格子,所以|A ∩ B| = n * m
。
把这三部分合起来,最终的答案就是: (所有行的集合数)+(所有列的集合数)- n * m
是不是一下子就清晰多啦?喵~ ✨
题解
下面就是具体的代码实现啦,本喵加了一些注释,方便主人理解哦。
#include <iostream>
#include <vector>
// 这个程序用容斥原理来解决“Rectangles”问题喵
// 通过计算满足条件的集合数量
int main() {
// 使用快速 I/O,让程序跑得更快一点~
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
// 读取棋盘的尺寸 n 和 m
int n, m;
std::cin >> n >> m;
// 读取棋盘数据,0 代表白色,1 代表黑色
std::vector<std::vector<int>> grid(n, std::vector<int>(m));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
std::cin >> grid[i][j];
}
}
// n 和 m 最大是 50,所以我们需要预计算 2 的幂次,最大到 2^50
// 2^50 很大,需要用 64 位的 long long 来存
std::vector<long long> pow2(51);
pow2[0] = 1;
for (int i = 1; i <= 50; ++i) {
pow2[i] = pow2[i - 1] * 2;
}
long long total_count = 0;
// 第一部分:计算所有在同一行内的集合数
// 对每一行,我们可以组成白色的非空子集和黑色的非空子集
for (int i = 0; i < n; ++i) {
int white_count = 0;
int black_count = 0;
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 0) {
white_count++;
} else {
black_count++;
}
}
// 加上这一行里白色格子的非空子集数量 (2^w - 1)
total_count += pow2[white_count] - 1;
// 加上这一行里黑色格子的非空子集数量 (2^b - 1)
total_count += pow2[black_count] - 1;
}
// 第二部分:计算所有在同一列内的集合数
// 同样地,对每一列进行计算
for (int j = 0; j < m; ++j) {
int white_count = 0;
int black_count = 0;
for (int i = 0; i < n; ++i) {
if (grid[i][j] == 0) {
white_count++;
} else {
black_count++;
}
}
// 加上这一列里白色格子的非空子集数量
total_count += pow2[white_count] - 1;
// 加上这一列里黑色格子的非空子集数量
total_count += pow2[black_count] - 1;
}
// 第三部分:应用容斥原理
// 上面的计算中,所有单个格子的集合被计算了两次(一次在行,一次在列)
// 所以我们要把重复计算的减掉。总共有 n * m 个单格子集合。
total_count -= (long long)n * m;
// 输出最终结果
std::cout << total_count << std::endl;
return 0;
}
知识点介绍
这道题主要用到了两个很基础但又很重要的知识点,本喵来给你讲讲吧!
1. 容斥原理 (Principle of Inclusion-Exclusion)
容斥原理是组合数学里一个非常萌也非常有用的工具,用来计算多个集合的并集大小。
最简单的情况就是两个集合 A 和 B: |A ∪ B| = |A| + |B| - |A ∩ B|
想象一下,有两个重叠的圆圈。如果我们想知道它们覆盖的总面积,直接把两个圆的面积加起来是不对的,因为中间重叠的部分被加了两次。所以,我们要减去一次重叠部分的面积。
在这道题里,|A|
就是所有“行集合”的数量,|B|
是所有“列集合”的数量。而 |A ∩ B|
就是那些既是“行集合”又是“列集合”的,也就是我们分析出来的单个格子的集合。为了不重复计算,就要把它减掉一次喵~
2. 子集 (Subsets)
一个集合如果有 k
个不同的元素,那么它总共有多少个不同的子集呢?
对于每个元素,我们都有两种选择:选它,或者不选它。根据乘法原理,k
个元素总共就有 2 * 2 * ... * 2
(k个2相乘) = 2^k
种选择,也就是 2^k
个子集。
这些子集里,包含了一个所有元素都不选的特殊情况,也就是空集。题目要求我们统计的是非空集合,所以要把空集去掉。
因此,一个有 k
个元素的集合,它的非空子集数量就是 2^k - 1
。
这就是为什么代码里计算 (pow2[count] - 1)
的原因啦。
好啦,今天的讲解就到这里啦!希望本喵的解释对主人有帮助。如果还有不明白的地方,随时可以再来问我哦!喵呜~ 💖