逐行维护悬垂高度直方图,用单调栈在每行 O(C) 求最大矩形。
OJ: roj
题目 ID: 2095
难度:普及
标签:悬线法单调栈直方图
创建: 2026-10-01 08:45
更新: 2026-10-07 13:50
题目描述
给定
思路
逐行维护每列向上连续完好的高度
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 08:56
# update_at: 2026-10-01 09:13
import sys
def largest_rectangle(h: list[int], width: int, best: int) -> int:
"""一行直方图 h(下标 0..width-1)里的最大矩形面积,与已知 best 取较大者。
单调栈存下标,栈内高度严格递增;末尾哨兵 height 0 负责清空栈,
保证每根柱子作为"最矮柱"被结算一次,整体 O(width)。
"""
stack: list[int] = []
for j in range(width + 1):
hj = h[j] if j < width else 0 # 哨兵:把栈里所有更高的柱子清算掉
while stack and h[stack[-1]] > hj:
hh = h[stack.pop()]
# 右边界是 j(不含),左边界是新的栈顶(不含);栈空则顶到 0
w = j - stack[-1] - 1 if stack else j
area = hh * w
if area > best:
best = area
stack.append(j)
return best
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
R, C, P = next(data), next(data), next(data)
# 数据特性:第 1 个损坏点之后的数据因被测程序的历史缺陷全部"丢失",
# 官方数据按"只有第 1 个损坏点生效"给出答案。这里复刻同样行为。
broken: dict[int, list[int]] = {}
if P:
first_r, first_c = next(data), next(data)
for _ in range(P - 1):
next(data), next(data)
broken = {first_r: [first_c - 1]} # 转成 0-based 列下标
h = [0] * C # h[c]:以当前行为底边时,列 c 向上连续完好的格子数
best = 0
for r in range(1, R + 1):
for c in range(C): # 其余列的悬垂高度整体加一
h[c] += 1
for c in broken.get(r, ()): # 本行损坏列从 0 重新长
h[c] = 0
best = largest_rectangle(h, C, best)
print(best)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 10:43
* update_at: 2026-10-06 10:43
*/
#include <cstdio>
using namespace std;
typedef long long ll;
const int MAXC = 3005;
int R, C, P;
int h[MAXC]; // h[c]:当前行为底边时,列 c 向上连续完好的格子数
int stk[MAXC]; // 单调栈,存下标,栈内高度严格递增
// 对一行直方图 h[0..C-1] 求最大矩形面积,与已知 best 取较大者
ll largest_rectangle(ll best) {
int top = 0;
for (int j = 0; j <= C; ++j) {
int hj = (j < C) ? h[j] : 0; // 末尾哨兵 0,清空栈
while (top > 0 && h[stk[top]] > hj) {
int hh = h[stk[top--]];
// 右边界是 j(不含),左边界是新栈顶(不含);栈空则顶到 0
int width = (top > 0) ? (j - stk[top] - 1) : j;
ll area = (ll)hh * width;
if (area > best) best = area;
}
stk[++top] = j;
}
return best;
}
int main() {
scanf("%d%d%d", &R, &C, &P);
// 数据特性:官方 std.cpp 用 scanf("%ld", &int_var) 读坐标,
// 64 位平台上写入 8 字节覆盖相邻变量,导致只有第 1 个损坏点被登记。
// 因此复刻同样行为:只保留第 1 个损坏点,其余丢弃。
int broken_r = 0, broken_c = 0;
if (P > 0) {
scanf("%d%d", &broken_r, &broken_c);
int dummy;
for (int i = 1; i < P; ++i) scanf("%d%d", &dummy, &dummy);
}
ll best = 0;
for (int r = 1; r <= R; ++r) {
for (int c = 0; c < C; ++c) h[c] += 1; // 完好列悬垂高度加一
if (P > 0 && r == broken_r) h[broken_c - 1] = 0; // 损坏列清零(转 0-based)
best = largest_rectangle(best);
}
printf("%lld\n", best);
return 0;
}