Day 010-Q3 — 乱択アルゴリズム

2026-04-23 黄色 / Phase 6 ★★★★★★ Randomized Quickselect

問題

$N$ 個の整数列 $A$、$Q$ 個のクエリ l r k に対して、$A[l..r]$ の中で $k$ 番目に小さい値を求めよ。

制約

$1 \le N \le 10^5$
$1 \le Q \le 10^5$
$1 \le l \le r \le N$
$1 \le k \le r-l+1$

入出力例

入力例 1

7 3
3 1 4 1 5 9 2
2 5 2
1 7 3
3 6 1

出力例 1

1
3
1

ヒント (段階的開示)

ヒント1: 方向性
Mo's Algorithm + 乱択、または Persistent Segment Tree。ここでは Randomized Quickselect。
ヒント2: アプローチ
乱択ピボットで分割して再帰的に絞り込む。期待計算量 $O(N)$。
ヒント3: 誘導
lo/mid/hi の3分割で重複要素を正しく扱う。

模範解答 (Python)

import sys
import random
input = sys.stdin.readline
sys.setrecursionlimit(500000)

def quickselect(arr, k):
    if len(arr) == 1:
        return arr[0]

    pivot = random.choice(arr)
    lo = [x for x in arr if x < pivot]
    mid = [x for x in arr if x == pivot]
    hi = [x for x in arr if x > pivot]

    if k <= len(lo):
        return quickselect(lo, k)
    elif k <= len(lo) + len(mid):
        return pivot
    else:
        return quickselect(hi, k - len(lo) - len(mid))

def solve():
    N, Q = map(int, input().split())
    A = list(map(int, input().split()))

    results = []
    for _ in range(Q):
        l, r, k = map(int, input().split())
        sub = A[l-1:r]
        results.append(quickselect(sub, k))

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

solve()

Step-by-Step 解説

1乱択の意義
決定論的だと最悪 $O(N^2)$。random.choice で期待値 $O(N)$ に収まる。
2三分割
lo / mid / hi で重複要素を正しく扱う。
3計算量
各クエリ期待 $O(r-l+1)$。$Q$ が多いと Persistent Segment Tree が最適。

よくあるミス

ミス原因正しい書き方
ピボット固定(先頭/末尾)ソート済み入力で $O(N^2)$random.choice(arr)
mid を考慮しない重複要素でバグlo/mid/hi の3分割
$k$ の補正忘れhi 再帰時にそのままk - len(lo) - len(mid)

次のステップ

  • 発展問題: Persistent Segment Tree + 座標圧縮で $O(N \log N + Q \log N)$
  • 応用: Introselect(最悪 $O(N)$ 保証)

自己評価

自分の回答

気づき・メモ