矩形 的题解
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求统计一个 的黑白矩阵中全为白格的矩形个数。核心解法是枚举矩形的上下两条边界,把「每一列在这两条边界之间是否全为白格」压缩成一个布尔数组并逐行增量维护,再把二维计数化归为一维的连续白段计数。
分析
核心观察
一个矩形由上下两条边界行与左右两条边界列唯一确定,它全为白格当且仅当其中的每个格子都是白格。固定行区间 之后,每一列能否被选为矩形的组成部分是彼此独立的:只要该列在
到
行之间没有出现黑格,它就可以参与;于是二维的判定退化为一维的连续段计数。
思路
枚举行区间的上边界 ,让下边界
从
起逐行向下扩展。维护一个布尔数组,其中
表示第
列在第
行到第
行之间是否全为白格。当
从
下移到
时,只有新加入的这一行会带来信息,更新规则是:第
行第
列为黑格时把
置为假,否则
保持原值。这样每组上下边界只需
时间更新,而不必重新扫描整个行区间。
固定 后,左右边界可以在连续的全白列中自由选取。若某一段极长全白列的长度是
,则左右端点都取自该段的矩形共有
个,相当于在 个列间隙中选两条分别作为左右边界。把数组里所有极长全白段按该式累加,就是行区间
贡献的矩形数。
正确性来自一一对应:每个全白矩形被且仅被一组 描述,程序枚举了全部
,并在每组
上通过极长连续段无重无漏地枚举全部
,因此累加值恰好是答案。
朴素做法枚举四个边界并对内部逐格检查,需要 甚至更高;上述做法把它降到
,
时约为
次基本操作。另有基于悬线法(柱状图高度 + 单调栈)的
做法:把每列向上连续的白色格子数当作柱高,逐行统计以该行为底边的矩形数,此处不再展开。
具体示例
以样例 为例,矩阵的四行依次是
WWBW、BBWB、WBWW、WBWB。
取行区间为第 行到第
行(只有串
WWBW):第 列是黑格,其余列全白,于是极长全白列段为
与
,长度分别是
与
,贡献
个矩形。
取行区间为第 行到第
行(串
WBWW 与 WBWB):第 列两行都是黑格,第
列在第
行是黑格,只有第
列与第
列在两行之间全白,两段长度都是
,各贡献
个,合计
个。
全部 个行区间的贡献依次为
,总和
,与样例输出一致。
算法步骤
- 读入
n,再读入行字符串存入
grid。 - 令计数变量
ans为。
- 枚举上边界
top:每开始一个新的top,都把长度为的布尔数组
white全部置为真。 - 枚举下边界
bottom从top到:扫描第
bottom行,凡是字符为B的列j,把white[j]置为假。 - 线性扫描
white,用run累计当前连续为真的列数;每遇到假值或扫描到末尾时,把run * (run + 1) / 2累加进ans,并把run清零。 - 输出
ans。
复杂度分析
- 时间:
。行区间共
组,每组更新与扫描各
;
时约
次操作。
- 空间:
。矩阵本身占
个字符,工作数组
white只需。
实现注意事项
- 计数变量必须使用
位整型。全白
时答案为
,约
;该值虽仍落在
位有符号整数(约
)范围内,但已占去可观额度,一旦实现中出现任何中间量的放大——例如先把段长取平方再累加、或按面积而非个数统计——就会溢出。统一用
long long计数可以彻底避开这类隐患。 - 段贡献公式先乘后除:
,
run * (run + 1)最大为,不会溢出,且两个相邻整数之积必为偶数,整除一定成立。
- 每开始一个新的上边界,都必须把
white整体重置为真,否则会残留上一组行区间的信息。 - 更新时只把黑格所在的列置为假,不要拿当前整行覆盖
white,否则会丢掉更长行区间上累积的结果;等价地写作。
- 输入每行是连续的
W/B字符、之间没有空格,用cin >> string或按整行读入都可以,列数恰为,无需再切分。
、全黑、全白等退化情形都被同一套逻辑覆盖:全黑时
run恒为,答案为
。
源代码
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
// 枚举矩形的上下边界,维护每一列在这两条边界之间是否全为白格,
// 再对连续全白的列段计数:长度为 len 的段提供 len*(len+1)/2 个矩形。
// 时间 O(n^3),空间 O(n^2)(矩阵存储)。
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::string> grid(n);
for (int i = 0; i < n; ++i) {
std::cin >> grid[i];
}
long long ans = 0;
std::vector<char> white(n, 0);
for (int top = 0; top < n; ++top) {
std::fill(white.begin(), white.end(), 1); // 尚未排除任何一列
for (int bottom = top; bottom < n; ++bottom) {
for (int j = 0; j < n; ++j) {
if (grid[bottom][j] == 'B') {
white[j] = 0; // 这一列在 [top, bottom] 内出现黑格
}
}
long long run = 0;
for (int j = 0; j < n; ++j) {
if (white[j]) {
++run;
} else {
ans += run * (run + 1) / 2;
run = 0;
}
}
ans += run * (run + 1) / 2;
}
}
std::cout << ans << '\n';
return 0;
}
import sys
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0])
grid = [data[1 + i].decode() for i in range(n)]
ans = 0
for top in range(n):
white = [True] * n
for bottom in range(top, n):
row = grid[bottom]
for j in range(n):
if row[j] == 'B':
white[j] = False
run = 0
for j in range(n):
if white[j]:
run += 1
else:
ans += run * (run + 1) // 2
run = 0
ans += run * (run + 1) // 2
sys.stdout.write(str(ans) + "\n")
main()
评论