E - 本棚の整理 / Organizing the Bookshelf Editorial by admin Editorial for printing
Claude 4.6 Opus (Thinking)概要
横一列に並んだ \(N\) 個の棚に対して、「区間に1冊ずつ追加」「1つの棚を0冊にリセット」「区間の合計を求める」の3種類の操作を効率的に処理する問題です。
考察
素朴なアプローチの問題点
各棚の冊数を配列で管理する場合: - 作業1(区間加算):\(O(r - l + 1)\) かかり、最悪 \(O(N)\) - 作業3(区間和):\(O(r - l + 1)\) かかり、最悪 \(O(N)\)
\(N, Q\) が最大 \(2 \times 10^5\) なので、合計 \(O(NQ)\) は最大 \(4 \times 10^{10}\) となりTLEします。
重要な気づき
各棚の値を「加算成分」と「調整成分」に分離して管理します。
- \(\text{add}[i]\) = 棚 \(i\) を含むすべての作業1による累積加算回数
- \(\text{adj}[i]\) = 棚 \(i\) に対する調整値(リセット操作で使用)
すると、棚 \(i\) の実際の冊数は \(\text{add}[i] + \text{adj}[i]\) と表せます。
各操作の処理
- 作業1 \((l, r)\):\(\text{add}[l..r]\) に一律 \(+1\)(区間加算)
- 作業2 \((p)\):実際の冊数を \(0\) にしたいので、\(\text{adj}[p] = -\text{add}[p]\) に更新(点更新)
- 作業3 \((l, r)\):\(\sum_{i=l}^{r}(\text{add}[i] + \text{adj}[i])\) を計算(区間和)
これにより、各成分に適したデータ構造を使い分けられます。
アルゴリズム
BIT(Fenwick Tree)を用いた高速処理
add 成分:区間加算 + 区間和クエリ
2本のBIT(\(B_1, B_2\))を使い、以下の公式で区間加算・区間和を実現します:
\[\text{prefix\_sum}(x) = B_1[x] \times x - B_2[x]\]
区間 \([l, r]\) に \(+1\) する際: - \(B_1\) に \(l\) で \(+1\)、\(r+1\) で \(-1\) - \(B_2\) に \(l\) で \(+(l-1)\)、\(r+1\) で \(-r\)
adj 成分:点更新 + 区間和クエリ
通常の1本のBIT(\(B_3\))で管理します。作業2で \(\text{adj}[p]\) を更新する際、差分 \(\Delta = \text{新しい adj}[p] - \text{古い adj}[p]\) を計算し、BITに反映します。現在の \(\text{adj}[p]\) の値は別の配列 adj_val に保持します。
具体例
棚が5個で、作業1 \((1, 3)\)、作業1 \((2, 4)\)、作業2 \((2)\)、作業3 \((1, 4)\) の場合:
- 作業1後:\(\text{add} = [1, 1, 1, 0, 0]\)
- 作業1後:\(\text{add} = [1, 2, 2, 1, 0]\)
- 作業2:\(\text{add}[2] = 2\) なので \(\text{adj}[2] = -2\) に設定
- 作業3:\((1+0) + (2+(-2)) + (2+0) + (1+0) = 1 + 0 + 2 + 1 = 4\)
計算量
- 時間計算量: \(O(Q \log N)\)(各操作で BIT を定数回呼び出し、各 BIT 操作は \(O(\log N)\))
- 空間計算量: \(O(N)\)(BIT 3本と調整値配列)
実装のポイント
add の点クエリ:作業2で棚 \(p\) の加算累積を知る必要があるため、\(\text{prefix\_sum}(p) - \text{prefix\_sum}(p-1)\) で計算します。
adj の差分管理:BIT は累積的に加算するデータ構造なので、\(\text{adj}[p]\) を新しい値に「設定」するには、古い値との差分をBITに加えます。そのため補助配列
adj_valが必要です。入力の高速読み込み:Python では
sys.stdin.buffer.read()を使い、出力もまとめて行うことで高速化しています。ソースコード
import sys
from math import sqrt
def main():
input_data = sys.stdin.buffer.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
Q = int(input_data[idx]); idx += 1
# sqrt decomposition approach
# Each position has a value = (number of type-1 additions that cover it) - (adjustments from type-2 resets)
#
# We maintain:
# - A BIT (Fenwick tree) for range-add (type 1) using difference array technique
# This tracks the cumulative additions from type 1 operations.
# - For type 2 (reset at position p), we need to know current value at p and subtract it.
# We can handle this by maintaining a separate "adjustment" array.
#
# Actually, let me think more carefully.
#
# Let add[i] = total number of type-1 operations that include position i (cumulative).
# Let adj[i] = adjustment value for position i (starts at 0).
# The actual value at position i = add[i] + adj[i].
#
# Type 1 (l, r): increment add[i] for all i in [l, r] by 1. (Range add)
# Type 2 (p): set adj[p] = -add[p] (so that actual value becomes 0). But adj[p] was something before.
# Actually: current value = add[p] + adj[p]. We want it to become 0, so adj[p] = -add[p].
# Type 3 (l, r): sum of (add[i] + adj[i]) for i in [l, r] = sum(add[l..r]) + sum(adj[l..r]).
#
# For add: we need range add and range sum. Use a BIT with range update/range query.
# For adj: we need point update and range sum. Use a simple BIT.
# BIT for range add, range sum (add array)
# Using two BITs B1, B2 such that prefix_sum(x) = B1[x]*x - B2[x]
B1 = [0] * (N + 2)
B2 = [0] * (N + 2)
def bit_update(bit, i, delta):
while i <= N:
bit[i] += delta
i += i & (-i)
def bit_query(bit, i):
s = 0
while i > 0:
s += bit[i]
i -= i & (-i)
return s
def range_add(l, r, val):
bit_update(B1, l, val)
bit_update(B1, r + 1, -val)
bit_update(B2, l, val * (l - 1))
bit_update(B2, r + 1, -val * r)
def prefix_sum_add(x):
return bit_query(B1, x) * x - bit_query(B2, x)
def range_sum_add(l, r):
return prefix_sum_add(r) - prefix_sum_add(l - 1)
# BIT for adj array (point update, range sum)
B3 = [0] * (N + 2)
def adj_update(i, delta):
while i <= N:
B3[i] += delta
i += i & (-i)
def adj_prefix_sum(i):
s = 0
while i > 0:
s += B3[i]
i -= i & (-i)
return s
def adj_range_sum(l, r):
return adj_prefix_sum(r) - adj_prefix_sum(l - 1)
# adj_val[i] stores the current adjustment for position i
adj_val = [0] * (N + 1)
out = []
for _ in range(Q):
t = int(input_data[idx]); idx += 1
if t == 1:
l = int(input_data[idx]); idx += 1
r = int(input_data[idx]); idx += 1
range_add(l, r, 1)
elif t == 2:
p = int(input_data[idx]); idx += 1
# current value at p = add[p] + adj_val[p], we want it to be 0
add_p = prefix_sum_add(p) - prefix_sum_add(p - 1)
new_adj = -add_p
delta = new_adj - adj_val[p]
adj_val[p] = new_adj
adj_update(p, delta)
else:
l = int(input_data[idx]); idx += 1
r = int(input_data[idx]); idx += 1
ans = range_sum_add(l, r) + adj_range_sum(l, r)
out.append(ans)
sys.stdout.write('\n'.join(map(str, out)) + '\n')
main()
この解説は claude4.6opus-thinking によって生成されました。
posted:
last update: