Day 024-Q5 — Treap (オンライン順序統計)

2026-05-07 赤色 Master / Phase 8+ ★★★★★★★★★ 平衡 BST / Treap

問題

オンラインクエリ(前の出力で XOR 復号): INSERT x, DELETE x, KMIN k, COUNT x, MERGE k1 k2

制約

$1 \le Q \le 2 \times 10^5$
$x, k \le 10^9$

入出力例

入力例 1

5
INSERT 10
INSERT 20
INSERT 15
KMIN 2
COUNT 15

出力例 1

15
2

ヒント (段階的開示)

ヒント1: 方向性
Treap(BST 順 + heap 順のランダム優先度)で期待深さ $O(\log N)$。各ノードに size を持たせて順序統計を実現。
ヒント2: アプローチ
主要操作: split_by_key, split_by_size, merge。これらで insert/delete/kth_min/count を実装。
ヒント3: 誘導
XOR 復号: x = (raw ^ last_ans) % MOD

模範解答 (Python)

import sys
import random
input = sys.stdin.readline
sys.setrecursionlimit(300000)
random.seed(42)

class Node:
    __slots__ = ['key', 'pri', 'left', 'right', 'size']
    def __init__(self, key):
        self.key = key
        self.pri = random.getrandbits(32)
        self.left = None
        self.right = None
        self.size = 1

def size(t):
    return t.size if t else 0

def update(t):
    if t:
        t.size = 1 + size(t.left) + size(t.right)

def merge(l, r):
    if l is None: return r
    if r is None: return l
    if l.pri > r.pri:
        l.right = merge(l.right, r); update(l); return l
    else:
        r.left = merge(l, r.left); update(r); return r

def split_by_key(t, key):
    if t is None: return None, None
    if t.key < key:
        l, r = split_by_key(t.right, key)
        t.right = l; update(t); return t, r
    else:
        l, r = split_by_key(t.left, key)
        t.left = r; update(t); return l, t

def split_by_size(t, k):
    if t is None: return None, None
    lsize = size(t.left)
    if lsize >= k:
        l, r = split_by_size(t.left, k)
        t.left = r; update(t); return l, t
    else:
        l, r = split_by_size(t.right, k - lsize - 1)
        t.right = l; update(t); return t, r

def insert(t, key):
    node = Node(key)
    l, r = split_by_key(t, key)
    return merge(merge(l, node), r)

def delete(t, key):
    if t is None: return None
    if t.key == key:
        return merge(t.left, t.right)
    elif key < t.key:
        t.left = delete(t.left, key)
    else:
        t.right = delete(t.right, key)
    update(t); return t

def kth_min(t, k):
    lsize = size(t.left)
    if k <= lsize: return kth_min(t.left, k)
    elif k == lsize + 1: return t.key
    else: return kth_min(t.right, k - lsize - 1)

def count_leq(t, x):
    if t is None: return 0
    if t.key <= x:
        return size(t.left) + 1 + count_leq(t.right, x)
    else:
        return count_leq(t.left, x)

def solve():
    Q = int(input())
    root = None
    last_ans = 0
    MOD = 10**9 + 7
    out = []
    for _ in range(Q):
        line = input().split()
        op = line[0]
        if op == 'INSERT':
            x = (int(line[1]) ^ last_ans) % MOD
            root = insert(root, x)
        elif op == 'DELETE':
            x = (int(line[1]) ^ last_ans) % MOD
            root = delete(root, x)
        elif op == 'KMIN':
            k = (int(line[1]) ^ last_ans) % MOD
            if k < 1 or k > size(root):
                out.append(-1); last_ans = -1
            else:
                ans = kth_min(root, k); out.append(ans); last_ans = ans
        elif op == 'COUNT':
            x = (int(line[1]) ^ last_ans) % MOD
            ans = count_leq(root, x); out.append(ans); last_ans = ans
        elif op == 'MERGE':
            k1 = (int(line[1]) ^ last_ans) % MOD
            k2 = (int(line[2]) ^ last_ans) % MOD
            if k1 > size(root):
                out.append(-1); last_ans = -1
            else:
                l, r = split_by_size(root, k1)
                if k2 < 1 or k2 > size(r):
                    out.append(-1); last_ans = -1
                    root = merge(l, r)
                else:
                    ans = kth_min(r, k2); out.append(ans); last_ans = ans
                    root = merge(l, r)
    print('\n'.join(map(str, out)))

solve()

Step-by-Step 解説

1Treap 構造
BST + max-heap(優先度はランダム)。期待深さ $O(\log N)$。
2merge/split
merge は優先度高い方を根、split はキーまたはサイズで分割。
3順序統計
各ノードに sizekth_min, count_leq ともに $O(\log N)$。
4オンライン処理
(raw ^ last_ans) % MOD でクエリ復号。

よくあるミス

ミス原因正しい書き方
size の更新忘れmerge/split 後 update なし全ての変更後に update(t)
split_by_size 境界lsize と k の比較lsize >= k → 左
再帰制限超過Python デフォルトsys.setrecursionlimit(300000)

次のステップ

  • Implicit Treap + lazy(区間反転・区間和)
  • AVL / 赤黒木 / スキップリスト比較

自己評価

自分の回答

気づき・メモ