E - ちょうどよい温度差 / Just the Right Temperature Difference 解説 by admin
Claude 4.6 Opus (Thinking)概要
連続する区間 \([l, r]\) のうち、区間内の最大値と最小値の差がちょうど \(K\) となるものの個数を求める問題です。「ちょうど \(K\)」を「\(K\) 以下」の個数から「\(K-1\) 以下」の個数を引くことで計算し、各カウントはスライディングウィンドウ+単調デックで効率的に求めます。
考察
素朴なアプローチの問題点
すべての区間 \([l, r]\) を列挙すると \(O(N^2)\) 通りあり、それぞれで最大値・最小値を求めると最悪 \(O(N^3)\) となり、\(N \leq 2 \times 10^5\) では到底間に合いません。
重要な気づき:「ちょうど \(K\)」を「以下」の差に変換する
「変動幅がちょうど \(K\)」の個数を直接数えるのは難しいですが、次の関係を使えばシンプルになります:
\[f(K) = (\text{変動幅が } K \text{ 以下の区間数}) - (\text{変動幅が } K-1 \text{ 以下の区間数})\]
「変動幅が \(K\) 以下の区間数」を効率よく数えられれば、2回呼び出して引き算するだけです。
尺取り法が使える理由
ある区間 \([l, r]\) の変動幅が \(K\) 以下であれば、その部分区間(\(r\) を小さくする、\(l\) を大きくする)も \(K\) 以下です。つまり、\(l\) を固定したとき \(r\) を右に伸ばしていくと、ある時点から変動幅が \(K\) を超え、それ以降ずっと超えたままになります。この単調性のおかげで尺取り法(2ポインタ)が使えます。
アルゴリズム
関数 count_at_most_k(A, N, K) の動作
2つの単調デックを用意する:
max_deq:区間内の最大値を効率的に管理する単調減少デック(先頭が最大値のインデックス)min_deq:区間内の最小値を効率的に管理する単調増加デック(先頭が最小値のインデックス)
右端 \(r\) を 0 から \(N-1\) まで順に進める:
- \(A[r]\) をデックに追加(単調性を保つように末尾から不要な要素を除去)
- 変動幅 \(> K\) の間、左端 \(l\) を1つずつ右にずらす。デックの先頭が \(l\) より左なら取り除く。
- このとき区間 \([l, r], [l+1, r], \dots, [r, r]\) はすべて変動幅 \(\leq K\) なので、個数 \(r - l + 1\) を加算。
最終的な答えは
count_at_most_k(A, N, K) - count_at_most_k(A, N, K-1)。
具体例
\(A = [3, 1, 4, 1, 5]\), \(K = 3\) の場合:
- 変動幅 \(\leq 3\) の区間数を数え、変動幅 \(\leq 2\) の区間数を数え、差を取ると「変動幅がちょうど 3」の区間数が得られます。
- 例えば区間 \([1, 3]\)(値 \(3, 1, 4\))は最大 \(4\)、最小 \(1\)、変動幅 \(3\) で条件を満たします。
計算量
- 時間計算量: \(O(N)\)(
count_at_most_k1回あたり。各要素はデックに最大1回追加・1回削除されるため。2回呼ぶので全体も \(O(N)\)) - 空間計算量: \(O(N)\)(デックと入力配列の分)
実装のポイント
\(K < 0\) の場合の処理: \(K - 1\) が負になる場合、変動幅(常に \(\geq 0\))が負以下になる区間は存在しないので 0 を返す。
デックにはインデックスを格納する: 値ではなくインデックスを入れることで、左端 \(l\) との比較で期限切れ要素を除去できる。
sys.stdin.buffer.read()を使うことで、Python でも高速に入力を読み込める。ソースコード
import sys
from collections import deque
def count_at_most_k(A, N, K):
"""変動幅が K 以下となる連続部分配列 [l, r] の個数を数える"""
if K < 0:
return 0
max_deq = deque() # decreasing
min_deq = deque() # increasing
left = 0
count = 0
for right in range(N):
while max_deq and A[max_deq[-1]] <= A[right]:
max_deq.pop()
max_deq.append(right)
while min_deq and A[min_deq[-1]] >= A[right]:
min_deq.pop()
min_deq.append(right)
while A[max_deq[0]] - A[min_deq[0]] > K:
left += 1
if max_deq[0] < left:
max_deq.popleft()
if min_deq[0] < left:
min_deq.popleft()
count += right - left + 1
return count
def main():
input_data = sys.stdin.buffer.read().split()
N = int(input_data[0])
K = int(input_data[1])
A = [int(input_data[i + 2]) for i in range(N)]
# 変動幅がちょうど K = (変動幅が K 以下) - (変動幅が K-1 以下)
result = count_at_most_k(A, N, K) - count_at_most_k(A, N, K - 1)
print(result)
main()
この解説は claude4.6opus-thinking によって生成されました。
投稿日時:
最終更新: