Day 067-Q2 — 最大矩形面積(ヒストグラム + Cartesian Tree)

2026-06-20 赤色 Master / Phase 8+ ★★★★★★★★★ Sparse Table RMQ + 分割統治

問題

$N$ 本の棒からなるヒストグラムが与えられる。$i$ 番目の棒の高さは $H_i$ である。$Q$ 個のクエリに答えよ。各クエリは $(l, r)$ で、区間 $[l, r]$ 内のヒストグラムにおける最大矩形面積を求めよ。

制約

パラメータ範囲
$N$$1 \le N \le 2 \times 10^5$
$Q$$1 \le Q \le 2 \times 10^5$
$H_i$$1 \le H_i \le 10^9$
$l, r$$0 \le l \le r < N$(0-indexed)

入出力例

入力例 1

6 3
2 1 5 6 2 3
0 5
2 4
3 5

出力例 1

10
10
6

全体[2,1,5,6,2,3]→高さ2×幅5=10。[5,6,2]→5×2=10。[6,2,3]→2×3=6。

概念図: Cartesian Tree + 分割統治による最大矩形

ヒストグラム [2, 1, 5, 6, 2, 3] の分割統治 高さ 2 i=0 1 min 5 i=2 6 i=3 2 i=4 3 i=5 1×6=6 2×5=10 ✓ Cartesian Tree(min heap) idx=1, H=1 idx=0, H=2 idx=3, H=6 分割: min のインデックスで左右に分割 全幅矩形: H[min] × (r-l+1) max(全幅, 左再帰, 右再帰) を返す Sparse Table RMQ 前処理: O(N log N) クエリ: O(1) で最小インデックス取得

ヒント(段階的開示)

ヒント1: 方向性
Sparse Table で区間最小インデックスを $O(1)$ で取得し、分割統治で最大矩形を求める。各クエリは区間最小で分割 → 3候補の最大値。
ヒント2: アプローチ

クエリ $(l, r)$ に対して:

  1. 区間 $[l, r]$ の最小値インデックス $m$ を $O(1)$ で取得(Sparse Table)
  2. $H[m] \times (r - l + 1)$(全域を $H[m]$ で押す矩形)
  3. $\text{solve}(l, m-1)$ と $\text{solve}(m+1, r)$ の再帰解
  4. 3つの最大値を返す
ヒント3: コード骨格
def solve(l, r):
    if l > r: return 0
    if l == r: return H[l]
    m = rmq(l, r)  # 最小値のインデックス
    return max(
        H[m] * (r - l + 1),
        solve(l, m - 1),
        solve(m + 1, r)
    )

模範解答 (Python)

import sys
from math import log2
input = sys.stdin.readline

def main():
    N, Q = map(int, input().split())
    H = list(map(int, input().split()))

    LOG = max(1, int(log2(N)) + 1) if N > 0 else 1
    sparse = [[i] * N for _ in range(LOG)]

    for j in range(1, LOG):
        for i in range(N - (1 << j) + 1):
            a, b = sparse[j-1][i], sparse[j-1][i + (1 << (j-1))]
            sparse[j][i] = a if H[a] <= H[b] else b

    def rmq(l, r):
        if l > r: return l
        k = int(log2(r - l + 1))
        a, b = sparse[k][l], sparse[k][r - (1 << k) + 1]
        return a if H[a] <= H[b] else b

    sys.setrecursionlimit(400010)

    def solve(l, r):
        if l > r: return 0
        if l == r: return H[l]
        m = rmq(l, r)
        full = H[m] * (r - l + 1)
        left = solve(l, m - 1)
        right = solve(m + 1, r)
        return max(full, left, right)

    out = []
    for _ in range(Q):
        l, r = map(int, input().split())
        out.append(solve(l, r))

    print('\n'.join(map(str, out)))

main()

Step-by-Step 解説

Step 1: Sparse Table 構築

区間最小インデックスを $O(N \log N)$ 前処理、$O(1)$ クエリで構築する。

Step 2: 分割統治

クエリ区間 $[l, r]$ の最小値インデックス $m$ を取り、3候補の最大値を返す: (1) $H[m] \times (r - l + 1)$、(2) solve(l, m-1)、(3) solve(m+1, r)。

Step 3: 計算量

処理計算量
Sparse Table 前処理$O(N \log N)$
solve 1クエリ(期待値)$O(N)$ 最悪
Qクエリ全体$O(Q \cdot N)$ 最悪

よくあるミス

ミス原因正しい書き方
l > r での再帰停止漏れ 空区間のベースケース欠落 if l > r: return 0
Sparse Table のインデックス越え r - (1 << k) + 1 < l になりうる k を int(log2(r-l+1)) で計算
再帰深度超過 N=2×10^5 で連鎖再帰 sys.setrecursionlimit を設定

次のステップ

発展: 永続 Cartesian Tree でクエリ $O(\log N)$ に改善。関連: Segment Tree でヒストグラム最大矩形を区間マージ(モノイド)で $O(\log N)$ クエリ。

自己評価