C - 区間の合計 / Sum of Intervals 解説 by admin
Claude 4.5 Opus概要
長さ \(N\) の正整数からなる数列から、要素の合計が \(K\) 以下となる連続部分列の個数を数える問題です。尺取り法(Two Pointers)を用いて効率的に解きます。
考察
素朴なアプローチとその問題点
すべての区間 \((l, r)\) を試す方法を考えると、区間の選び方が \(O(N^2)\) 通りあり、各区間の合計計算に \(O(N)\) かかると全体で \(O(N^3)\) となります。累積和を使っても \(O(N^2)\) で、\(N = 2 \times 10^5\) では TLE になります。
重要な気づき:単調性
この問題では すべての要素が正 という条件があります。これにより以下の性質が成り立ちます:
- 区間を広げると合計は必ず増える
- 区間を狭めると合計は必ず減る
この単調性を利用すると、尺取り法が適用できます。
尺取り法の着想
右端を \(r\) に固定したとき、「合計が \(K\) 以下となる最小の左端 \(l\)」を求められれば、左端が \(l, l+1, \ldots, r\) のどれでも条件を満たします(区間を狭めると合計が減るため)。
さらに、\(r\) を 1 増やすと合計が増えるので、新しい最小の左端は前の左端以上になります。つまり、左端は右にしか動かないので、全体で \(O(N)\) で処理できます。
アルゴリズム
- 左端
left = 0、現在の区間の合計current_sum = 0、答えcount = 0で初期化 - 右端
rightを \(0\) から \(N-1\) まで順に動かす:current_sumにA[right]を加えるcurrent_sum > Kの間、A[left]を引いてleftを右に進める- この時点で区間 \([left, right]\) の合計は \(K\) 以下
- 右端が
rightで条件を満たす区間はright - left + 1個 countにこの個数を加算
countを出力
具体例(\(N=5, K=10, A=[3, 1, 4, 1, 5]\))
| right | A[right] | current_sum | left | 条件を満たす区間数 |
|---|---|---|---|---|
| 0 | 3 | 3 | 0 | 1([3]) |
| 1 | 1 | 4 | 0 | 2([3,1], [1]) |
| 2 | 4 | 8 | 0 | 3([3,1,4], [1,4], [4]) |
| 3 | 1 | 9 | 0 | 4 |
| 4 | 5 | 14→10 | 1 | 4([1,4,1,5]など) |
答え: \(1+2+3+4+4 = 14\)
計算量
- 時間計算量: \(O(N)\)
rightは \(N\) 回進み、leftも合計で高々 \(N\) 回しか進まないため
- 空間計算量: \(O(N)\)
- 配列 \(A\) の格納に必要
実装のポイント
left <= rightの条件を while ループに入れることで、すべての要素が \(K\) より大きい場合も正しく処理できるcurrent_sumとKは最大 \(10^{14}\) 程度になるため、64ビット整数が必要(Pythonでは自動で対応)要素がすべて正なので、
current_sum <= Kを満たせばleftからrightまでのすべての区間が条件を満たすソースコード
def solve():
N, K = map(int, input().split())
A = list(map(int, input().split()))
count = 0
current_sum = 0
left = 0
for right in range(N):
current_sum += A[right]
# 合計がKを超える間、左端を右に移動
while current_sum > K and left <= right:
current_sum -= A[left]
left += 1
# left から right までの区間で、合計がK以下
# この時、右端がrightで左端がleft, left+1, ..., right となる区間はすべてK以下
# (すべての要素が正なので、区間を狭めれば合計は減る)
if current_sum <= K:
count += (right - left + 1)
print(count)
solve()
この解説は claude4.5opus によって生成されました。
投稿日時:
最終更新: