Day 082-Q5 — オンライン平面点追加・矩形カウント(Fractional Cascading + Merge Sort Tree)

2026-07-05 赤色 Master / Phase 8+ ★★★★★★★★★ 動的 Merge Sort Tree・2D 点クエリ・$O(Q \log^2 Q)$

問題

2次元平面上でオンライン操作を処理せよ:

  • add x y: 点 $(x, y)$ を追加
  • count x1 y1 x2 y2: $x_1 \le x \le x_2$ かつ $y_1 \le y \le y_2$ を満たす点の個数を答える(XOR オンライン形式)

制約

パラメータ範囲備考
$Q$$\le 2 \times 10^5$クエリ数
$x, y$$0 \le x, y \le 10^9$座標
目標$O(Q \log^2 Q)$以下

入出力例

入力例1

8
A 1 2
A 3 4
A 2 3
C 1 1 3 4
A 4 1
C 1 0 4 3
A 0 0
C 0 0 5 5

出力例1

3
2
5

概念図: 動的 Merge Sort Tree の構造

動的 Merge Sort Tree — x軸セグメント木 × y座標ソートリスト root [0, 10^9] ys=[1,2,3,4] ←全点の y left [0, 5×10^8] ys=[2,3,4] ←x∈[0,5×10^8]の点 right [5×10^8+1, 10^9] ys=[1] ←x∈右半分の点 [0, 2.5×10^8] ys=[2,3] [2.5×10^8+1, 5×10^8] ys=[4] count クエリ [x1,x2] × [y1,y2] 1. x軸セグ木で [x1,x2] を O(log C) ノードに分解 2. 各ノードの ys で bisect により [y1,y2] 内の点数を O(log Q) で取得 合計: O(log^2 C) per query ✓ add(x, y): 根から葉への経路上の全ノードに y を insort 深さ O(log C) 段 × insort O(log Q) = O(log^2 C) per add ポインタ木なので未訪問ノードはメモリ割り当て不要

ヒント

ヒント1(方向性)

オフライン版(全クエリが事前判明)なら座標圧縮 + 2D BIT で解けるが、オンライン(add が動的)では工夫が必要。動的 Merge Sort Tree(セグメント木の各ノードにソート済みリストを持つ構造)を使う。

ヒント2(アプローチ)

$x$ 座標方向にポインタベースのセグメント木を構築(座標圧縮なし、必要時にノードを生成)。各ノードは「カバー範囲内に追加された点の $y$ 座標の動的ソートリスト」を持つ。count クエリは $x$ 方向のセグメント木区間分解後、各ノードで $[y_1, y_2]$ の点数を二分探索で求める。

ヒント3(誘導)
import bisect

class Node:
    __slots__ = ['left', 'right', 'ys']
    def __init__(self):
        self.left = self.right = None
        self.ys = []

def add(node, lo, hi, x, y):
    bisect.insort(node.ys, y)  # ソート維持挿入
    if lo == hi: return
    mid = (lo + hi) >> 1
    if x <= mid:
        if node.left is None: node.left = Node()
        add(node.left, lo, mid, x, y)
    else:
        if node.right is None: node.right = Node()
        add(node.right, mid+1, hi, x, y)

模範解答

import sys
import bisect
input = sys.stdin.readline

def solve():
    Q = int(input())

    class Node:
        __slots__ = ['left', 'right', 'ys']
        def __init__(self):
            self.left = self.right = None
            self.ys = []

    X_MAX = 10**9

    def add(node, lo, hi, x, y):
        bisect.insort(node.ys, y)
        if lo == hi: return
        mid = (lo + hi) >> 1
        if x <= mid:
            if node.left is None: node.left = Node()
            add(node.left, lo, mid, x, y)
        else:
            if node.right is None: node.right = Node()
            add(node.right, mid+1, hi, x, y)

    def query(node, lo, hi, x1, x2, y1, y2):
        if node is None or x2 < lo or hi < x1: return 0
        if x1 <= lo and hi <= x2:
            return bisect.bisect_right(node.ys, y2) - bisect.bisect_left(node.ys, y1)
        mid = (lo + hi) >> 1
        return (query(node.left, lo, mid, x1, x2, y1, y2) +
                query(node.right, mid+1, hi, x1, x2, y1, y2))

    root = Node()
    xor_acc = 0
    out = []

    for _ in range(Q):
        line = input().split()
        op = line[0]
        if op == 'A':
            x, y = int(line[1]), int(line[2])
            add(root, 0, X_MAX, x, y)
        else:
            x1 = int(line[1]) ^ xor_acc
            y1 = int(line[2]) ^ xor_acc
            x2 = int(line[3]) ^ xor_acc
            y2 = int(line[4]) ^ xor_acc
            if x1 > x2: x1, x2 = x2, x1
            if y1 > y2: y1, y2 = y2, y1
            ans = query(root, 0, X_MAX, x1, x2, y1, y2)
            out.append(ans)
            xor_acc = ans

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

solve()

Step-by-Step 解説

Step 1: 動的 Merge Sort Tree の構造

各ノードはカバー範囲内に追加された全点の $y$ 座標をソート済みリストで保持する。ポインタベースにより未訪問ノードはメモリ不使用。

Step 2: add 操作

$x$ の位置に対して根から葉まで降りる経路上の全ノード(深さ $O(\log C)$)の ys に $y$ を bisect.insort で挿入。

Step 3: count クエリ

$x$ 区間を $O(\log C)$ ノードに分解し、各ノードの ys で $[y_1, y_2]$ 内の要素数を二分探索($O(\log Q)$)で求める。合計 $O(\log^2 C)$。

Step 4: 計算量まとめ

操作時間空間(追加分)
add$O(\log^2 C)$$O(\log C)$
count$O(\log^2 C)$
全体$O(Q \log^2 C)$$O(Q \log C)$

よくあるミス

ミス原因正しい書き方
x1 > x2 の矩形XOR 復号後に大小関係が逆転if x1 > x2: x1, x2 = x2, x1
query でノードが None を参照ポインタ木のため未訪問ノードがNoneif node is None: return 0
Python list bisect.insort が遅い$O(N)$ シフトが発生SortedContainers の SortedList を使う
x_max を 10^9 に固定問題によっては超過実際の最大座標 + 1 に設定

次のステップ

  • 発展問題: 同様の構造で「加重点」のカウント(各点に重みがある場合)
  • 参考: Fractional Cascading を使うと $O(\log C)$ クエリも理論的に可能(実装が複雑)

自己評価

理解度: / /

自分の回答:

気づき・メモ: