Day 007-Q1 — 遅延伝播セグメント木

2026-04-20 青色 / Phase 5 ★★★★★ 遅延伝播セグメント木

問題

長さ $N$ の数列 $A$ がある。以下の $Q$ 個のクエリを処理せよ。

  • 1 l r x : $A_l, \ldots, A_r$ の全要素に $x$ を加算
  • 2 l r : $A_l, \ldots, A_r$ の最大値を出力

制約

$1 \le N, Q \le 2\times10^5$
$-10^9 \le A_i \le 10^9$
$1 \le l \le r \le N$
$-10^9 \le x \le 10^9$

入出力例

入力例 1

5 5
1 2 3 4 5
1 1 3 10
2 1 5
1 3 5 -2
2 2 4
2 1 5

出力例 1

15
13
13

ヒント (段階的開示)

ヒント1: 方向性
区間更新(加算)と区間最大値クエリを両方 $O(\log N)$ で処理する。
ヒント2: アプローチ
「遅延」タグを各ノードに持たせ、必要になるまで子への伝播を遅らせる。
ヒント3: 誘導
def _push_down(self, node):
    if self.lazy[node] != 0:
        for child in [2*node, 2*node+1]:
            self.tree[child] += self.lazy[node]
            self.lazy[child] += self.lazy[node]
        self.lazy[node] = 0

模範解答 (Python)

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

class LazySegTree:
    def __init__(self, a):
        self.n = len(a)
        self.tree = [-inf] * (4 * self.n)
        self.lazy = [0] * (4 * self.n)
        self._build(a, 1, 0, self.n - 1)

    def _build(self, a, node, start, end):
        if start == end:
            self.tree[node] = a[start]
            return
        mid = (start + end) // 2
        self._build(a, 2*node, start, mid)
        self._build(a, 2*node+1, mid+1, end)
        self.tree[node] = max(self.tree[2*node], self.tree[2*node+1])

    def _push_down(self, node):
        if self.lazy[node] != 0:
            for child in [2*node, 2*node+1]:
                self.tree[child] += self.lazy[node]
                self.lazy[child] += self.lazy[node]
            self.lazy[node] = 0

    def update(self, l, r, val, node=1, start=None, end=None):
        if start is None:
            start, end = 0, self.n - 1
        if r < start or end < l:
            return
        if l <= start and end <= r:
            self.tree[node] += val
            self.lazy[node] += val
            return
        self._push_down(node)
        mid = (start + end) // 2
        self.update(l, r, val, 2*node, start, mid)
        self.update(l, r, val, 2*node+1, mid+1, end)
        self.tree[node] = max(self.tree[2*node], self.tree[2*node+1])

    def query(self, l, r, node=1, start=None, end=None):
        if start is None:
            start, end = 0, self.n - 1
        if r < start or end < l:
            return -inf
        if l <= start and end <= r:
            return self.tree[node]
        self._push_down(node)
        mid = (start + end) // 2
        return max(self.query(l, r, 2*node, start, mid),
                   self.query(l, r, 2*node+1, mid+1, end))

def main():
    N, Q = map(int, input().split())
    A = list(map(int, input().split()))
    seg = LazySegTree(A)
    for _ in range(Q):
        q = list(map(int, input().split()))
        if q[0] == 1:
            _, l, r, x = q
            seg.update(l-1, r-1, x)
        else:
            _, l, r = q
            print(seg.query(l-1, r-1))

main()

Step-by-Step 解説

1通常のセグメント木との違い
通常のセグ木は点更新のみ。区間更新を素朴に行うと $O(N \log N)$ かかる。遅延伝播で区間更新も $O(\log N)$。
2lazy の役割
lazy[node] = 「このノードの子にまだ伝播していない加算値」。ノード自身の tree[node] はすでに更新済み。
3push_down のタイミング
子にアクセスする前に必ず _push_down を呼ぶ。

よくあるミス

ミス原因正しい書き方
push_down を query でも忘れるupdate だけ気にしがちquery の分岐前にも _push_down
1-indexed と 0-indexed の混在問題は 1-indexedl-1, r-1 で変換
lazy のリセット忘れ子に渡した後に 0 にしないself.lazy[node] = 0 必須

次のステップ

  • 発展問題: 区間の要素を $x$ に「代入」するクエリ(assign lazy)と区間最小値クエリ

自己評価

自分の回答

気づき・メモ