問題
長さ $N$ の数列 $A$ と $Q$ 個のクエリ:
1 i x: $A[i]$ に $x$ を加える2 t l r: バージョン $t$ における $A[l]+\ldots+A[r]$ を出力
バージョン 0 は初期状態。1 操作のたびにバージョンが増える。
制約
$1 \le N \le 10^5$
$1 \le Q \le 10^5$
$-10^9 \le A[i] \le 10^9$
入出力例
入力例 1
5 7
1 2 3 4 5
1 3 10
1 1 -1
2 0 1 5
2 1 2 4
2 2 1 3
出力例 1
15
19
15
ヒント (段階的開示)
ヒント1: 方向性
通常のセグ木は最新状態のみ。Persistent Segment Tree は各更新で新ルートを作り、変更パスだけコピー。
ヒント2: アプローチ
ノードオブジェクトで管理。更新時はルートから対象リーフへのパス上のノード($O(\log N)$ 個)のみ新規作成。
ヒント3: 誘導
def update(prev, l, r, i, x):
node = Node(prev.l, prev.r, prev.val + x)
if l == r: return node
mid = (l + r) // 2
if i <= mid:
node.l = update(prev.l, l, mid, i, x)
else:
node.r = update(prev.r, mid+1, r, i, x)
return node
模範解答 (Python)
import sys
input = sys.stdin.readline
sys.setrecursionlimit(300000)
class Node:
__slots__ = ['l', 'r', 'val']
def __init__(self, l=None, r=None, val=0):
self.l = l
self.r = r
self.val = val
def build(arr, l, r):
node = Node()
if l == r:
node.val = arr[l]
return node
mid = (l + r) // 2
node.l = build(arr, l, mid)
node.r = build(arr, mid+1, r)
node.val = node.l.val + node.r.val
return node
def update(prev, l, r, i, x):
node = Node(prev.l, prev.r, prev.val + x)
if l == r:
return node
mid = (l + r) // 2
if i <= mid:
node.l = update(prev.l, l, mid, i, x)
else:
node.r = update(prev.r, mid+1, r, i, x)
return node
def query(node, l, r, ql, qr):
if ql > r or qr < l:
return 0
if ql <= l and r <= qr:
return node.val
mid = (l + r) // 2
return query(node.l, l, mid, ql, qr) + query(node.r, mid+1, r, ql, qr)
def solve():
N, Q = map(int, input().split())
A = [0] + list(map(int, input().split()))
roots = [None] * (Q + 1)
roots[0] = build(A, 1, N)
ver = 0
for _ in range(Q):
tokens = list(map(int, input().split()))
if tokens[0] == 1:
_, i, x = tokens
roots[ver+1] = update(roots[ver], 1, N, i, x)
ver += 1
else:
_, t, l, r = tokens
print(query(roots[t], 1, N, l, r))
solve()
Step-by-Step 解説
1永続化の核心
ノードをオブジェクトで持ち、更新時にパス上のノードだけコピー。$O(\log N)$ 時間・空間で新バージョン。
ノードをオブジェクトで持ち、更新時にパス上のノードだけコピー。$O(\log N)$ 時間・空間で新バージョン。
2build
$N$ 要素で通常の再帰セグメント木を構築。$O(N)$。
$N$ 要素で通常の再帰セグメント木を構築。$O(N)$。
3update
ルートから $i$ への経路($O(\log N)$ ノード)だけ新ノードを作成。
ルートから $i$ への経路($O(\log N)$ ノード)だけ新ノードを作成。
4query
バージョン $t$ のルートから通常の区間和クエリ。
バージョン $t$ のルートから通常の区間和クエリ。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
roots[ver+1] = roots[ver] で上書き | 永続化していない | 新ノードを作成して返す |
| 再帰深度超過 | $N=10^5$ で深さ17 | setrecursionlimit(300000) |
| バージョン番号ずれ | 0始まり vs 1始まり | roots[0] を初期状態として明示 |
次のステップ
- 発展問題: Persistent Segment Tree + 座標圧縮で「バージョン $l$〜$r$ における $k$ 番目の要素」
- 応用: 永続 Union-Find