問題
長さ $N$ の整数列 $A = [a_1, a_2, \ldots, a_N]$ に対し、$Q$ 個のクエリを処理せよ。
- クエリ型 1:
1 l r— $A[l..r]$(1-indexed, 閉区間)を逆順にする。 - クエリ型 2:
2 l r— $A[l..r]$ の最大値を出力する。 - クエリ型 3:
3 i v— $A[i]$ を $v$ に変更する。
制約
| パラメータ | 範囲 |
|---|---|
| $N$ | $1 \le N \le 2 \times 10^5$ |
| $Q$ | $1 \le Q \le 2 \times 10^5$ |
| $a_i, v$ | $1 \le a_i, v \le 10^9$ |
| $l, r, i$ | $1 \le l \le r \le N$ |
入出力例
入力例 1
6 5
3 1 4 1 5 9
1 2 5
2 1 6
3 3 7
1 1 6
2 2 5
出力例 1
9
7
1 2 5 後: [3,5,1,4,1,9]。最大=9。3 3 7 後: [3,5,7,4,1,9]。1 1 6 後: [9,1,4,7,5,3]。2 2 5 → max(1,4,7,5)=7。
概念図: Implicit Treap の split/merge
ヒント(段階的開示)
ヒント1: 方向性
区間逆転・最大値クエリ・点更新を同時に扱うには暗黙的Treap(Implicit Treap)が最適。配列の添字を sz から暗黙に計算し、split/merge で $O(\log N)$ 期待値で全クエリを処理できる。
ヒント2: アプローチ
- 各ノード:
val, pri, sz, mx, rev, left, right split(t, k): 左k個 / 残りに分割。左子の sz と k を比較して再帰merge(l, r): pri の大小で根を決め、残りを再帰的にマージ- 区間逆転: t2.rev ^= True、push_down 時に左右子を交換
ヒント3: コード骨格
def push_down(t):
if t and t.rev:
t.left, t.right = t.right, t.left
if t.left: t.left.rev ^= True
if t.right: t.right.rev ^= True
t.rev = False
def split(t, k):
if t is None: return None, None
push_down(t)
lsz = sz(t.left)
if lsz >= k:
l, t.left = split(t.left, k)
pull_up(t); return l, t
else:
t.right, r = split(t.right, k - lsz - 1)
pull_up(t); return t, r
模範解答 (Python)
import sys
import random
input = sys.stdin.readline
class Node:
__slots__ = ['val','pri','sz','mx','rev','left','right']
def __init__(self, val):
self.val = val
self.pri = random.random()
self.sz = 1
self.mx = val
self.rev = False
self.left = self.right = None
def _sz(t): return t.sz if t else 0
def _mx(t): return t.mx if t else -1
def push_down(t):
if t and t.rev:
t.left, t.right = t.right, t.left
if t.left: t.left.rev ^= True
if t.right: t.right.rev ^= True
t.rev = False
def pull_up(t):
if t:
t.sz = 1 + _sz(t.left) + _sz(t.right)
t.mx = max(t.val, _mx(t.left), _mx(t.right))
def split(t, k):
if t is None:
return None, None
push_down(t)
lsz = _sz(t.left)
if lsz >= k:
l, t.left = split(t.left, k)
pull_up(t)
return l, t
else:
t.right, r = split(t.right, k - lsz - 1)
pull_up(t)
return t, r
def merge(l, r):
if l is None: return r
if r is None: return l
push_down(l); push_down(r)
if l.pri > r.pri:
l.right = merge(l.right, r)
pull_up(l)
return l
else:
r.left = merge(l, r.left)
pull_up(r)
return r
def main():
sys.setrecursionlimit(500000)
N, Q = map(int, input().split())
A = list(map(int, input().split()))
root = None
for v in A:
root = merge(root, Node(v))
out = []
for _ in range(Q):
line = list(map(int, input().split()))
if line[0] == 1:
_, l, r = line
l -= 1
t1, t2 = split(root, l)
t2, t3 = split(t2, r - l)
t2.rev ^= True
root = merge(t1, merge(t2, t3))
elif line[0] == 2:
_, l, r = line
l -= 1
t1, t2 = split(root, l)
t2, t3 = split(t2, r - l)
out.append(_mx(t2))
root = merge(t1, merge(t2, t3))
else:
_, i, v = line
i -= 1
t1, t2 = split(root, i)
t2, t3 = split(t2, 1)
t2.val = v
pull_up(t2)
root = merge(t1, merge(t2, t3))
print('\n'.join(map(str, out)))
main()
Step-by-Step 解説
Step 1: 暗黙的 Treap の基本構造
Treap はランダム優先度 pri で heap 性質を持つ BST。「暗黙的」とは添字をキーとせず、sz(部分木サイズ)から暗黙に位置を計算する点。これにより「k番目の要素」へのアクセスが $O(\log N)$ で可能になる。
Step 2: split(t, k) の動作
左部分木サイズ lsz = sz(t.left) と k を比較する。
| 条件 | 動作 |
|---|---|
| lsz ≥ k | t の添字は k より右 → 左子を再帰 split |
| lsz < k | t 自身が左側の (lsz+1) 番目 → 右子を残り k-lsz-1 個分 split |
Step 3: 遅延反転フラグ
t.rev = True はこのノードの部分木が逆順になっていることを示す遅延フラグ。push_down 時に左右子を交換し、子に rev を伝播させることで $O(\log N)$ で区間逆転が実現する。
Step 4: クエリ処理フロー
| クエリ | 手順 |
|---|---|
| 逆転 [l,r] | split(l-1) → t1, t2 | split(r-l+1) → t2, t3 | t2.rev^=True | merge |
| 最大値 [l,r] | split × 2 → t2.mx を読む → merge |
| 点更新 i | split(i-1) → split(1) → t2.val=v, pull_up → merge |
Step 5: 計算量
| 操作 | 計算量 |
|---|---|
| split / merge | $O(\log N)$ 期待値 |
| 全クエリ | $O(Q \log N)$ 期待値 |
| 空間 | $O(N)$ |
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| push_down を忘れる | rev フラグが子に伝わらない | split/merge の先頭で必ず push_down |
| pull_up を忘れる | sz や mx が更新されない | 子の変更後に必ず pull_up |
| split の境界値ミス | 0-indexed/1-indexed の混在 | split(root, l) で「左 l 個」を意識 |
| 再帰深度超過 | Python のデフォルト制限 | sys.setrecursionlimit(500000) |
次のステップ
発展問題: 暗黙的 Treap で区間ソート(マージソートとの融合)、または永続 Treap(バージョン管理)。関連: RBST(Randomized BST)との比較実装。