問題
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 の構造
ヒント
ヒント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 を参照 | ポインタ木のため未訪問ノードがNone | if node is None: return 0 |
| Python list bisect.insort が遅い | $O(N)$ シフトが発生 | SortedContainers の SortedList を使う |
| x_max を 10^9 に固定 | 問題によっては超過 | 実際の最大座標 + 1 に設定 |
次のステップ
- 発展問題: 同様の構造で「加重点」のカウント(各点に重みがある場合)
- 参考: Fractional Cascading を使うと $O(\log C)$ クエリも理論的に可能(実装が複雑)
自己評価
理解度: / /
自分の回答:
気づき・メモ: