C - バッテリー残量 / Battery Remaining Editorial by admin Editorial for printing
GPT 5.2 High概要
各区間 \([L,R]\) に対して「その区間の全要素を \(1\) 減らす」操作が \(Q\) 回行われるので、各スマートフォンが合計で何回減らされるかを高速に数え、最後に \(S_i-\text{回数}\) を \(0\) 未満にならないように丸めて出力します。
考察
重要な気づき
各スマートフォン \(i\) の最終残量は、結局 - 「イベントがスマホ \(i\) を何回含むか」を \(c_i\) とすると - 最終的に \(S_i' = \max(0,\, S_i - c_i)\)
となります。イベントの順番や途中で \(0\) になったかどうかは、最終結果に対しては \(S_i\) から合計減少回数を引いて \(0\) で下限を取る だけで良い、という点がポイントです(\(0\) 以下にはならないので)。
素朴解がなぜ遅いか
各イベントごとに区間 \([L,R]\) を全部走査して \(1\) ずつ減らすと、最悪で - 1回のイベントで \(O(N)\) - それが \(Q\) 回で \(O(NQ)\)
となり、\(N,Q \le 2\times 10^5\) では間に合いません。
どう解決するか
必要なのは「各位置が区間更新に何回含まれたか」だけなので、区間加算を高速に処理できる 差分配列(いもす法) を使い、各 \(c_i\) を \(O(N+Q)\) で求めます。
アルゴリズム
差分配列 diff を長さ \(N+1\) で用意し、各イベント \([L,R]\)(1-indexed, 両端含む)について次を行います(0-indexedに直して処理):
- \(L' = L-1\) として
diff[L'] += 1 - 区間の右端の「次」から影響を打ち消すために、\(R\) が \(N\) 未満なら
diff[R] -= 1
(ここでRは 1-indexed の右端なので、0-indexed では「右端の次」の位置に対応します)
その後、左から順に累積和 cur を取り、
- cur += diff[i] により \(c_i = cur\) を得る
- 最終値は S[i] = max(0, S[i] - cur) とする
具体例
\(N=5\)、イベントが \([2,4]\) の1回だけだとします。
- diff[1] += 1(2番目から減る)
- diff[4] -= 1(4番目の次=5番目からは減らない)
diff の累積和は [0,1,1,1,0] になり、2〜4番目だけ1回減ることが分かります。
計算量
- 時間計算量: \(O(N+Q)\)(差分更新が \(Q\) 回、累積和が \(N\) 回)
- 空間計算量: \(O(N)\)(差分配列)
実装のポイント
入力の \([L,R]\) は 1-indexed なので、左端だけ
L -= 1して 0-indexed に合わせる。diff[R] -= 1は \(R < N\) のときだけ 行う(範囲外アクセス防止)。最終値は
max(0, ...)で \(0\) 未満にならないようにする。ソースコード
import sys
def main():
input = sys.stdin.readline
N, Q = map(int, input().split())
S = list(map(int, input().split()))
diff = [0] * (N + 1)
for _ in range(Q):
L, R = map(int, input().split())
L -= 1
diff[L] += 1
if R < N:
diff[R] -= 1
cur = 0
for i in range(N):
cur += diff[i]
v = S[i] - cur
S[i] = v if v > 0 else 0
sys.stdout.write(" ".join(map(str, S)))
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。
posted:
last update: