C - ペアの合計点 / Total Score of Pairs 解説 by admin
Claude 4.6 Opus (Thinking)概要
\(N\) 人の生徒から異なる2人を選ぶペアのうち、2人の得点の合計が基準値 \(K\) 以上となるペアの個数を効率的に数える問題です。
考察
素朴なアプローチとその問題点
最も単純な方法は、すべてのペア \((i, j)\)(\(i < j\))について \(A_i + A_j \geq K\) かどうかを調べることです。しかし、ペアの総数は \(\frac{N(N-1)}{2}\) 個あり、\(N = 2 \times 10^5\) のとき約 \(2 \times 10^{10}\) 個になります。これでは時間制限に間に合いません(TLE)。
重要な気づき
条件 \(A_i + A_j \geq K\) を変形すると、\(A_j \geq K - A_i\) となります。つまり、ある生徒 \(i\) を固定したとき、もう一方の生徒の得点が \(K - A_i\) 以上であればよいということです。
配列をソートしておけば、\(K - A_i\) 以上の要素がどこから始まるかを二分探索で高速に求められます。これにより、各生徒 \(i\) に対して条件を満たす相手の人数を \(O(\log N)\) で求められます。
具体例
例えば \(N = 4\), \(K = 7\), \(A = [2, 5, 3, 8]\) の場合を考えます。
ソート後: \(A = [2, 3, 5, 8]\)
- \(i = 0\)(\(A_i = 2\)): \(A_j \geq 7 - 2 = 5\) を満たす \(j > 0\) の個数 → \(A[2]=5, A[3]=8\) の2個
- \(i = 1\)(\(A_i = 3\)): \(A_j \geq 7 - 3 = 4\) を満たす \(j > 1\) の個数 → \(A[2]=5, A[3]=8\) の2個
- \(i = 2\)(\(A_i = 5\)): \(A_j \geq 7 - 5 = 2\) を満たす \(j > 2\) の個数 → \(A[3]=8\) の1個
- \(i = 3\)(\(A_i = 8\)): \(j > 3\) の範囲に要素なし → 0個
合計: \(2 + 2 + 1 + 0 = 5\) 個
アルゴリズム
- 配列 \(A\) を昇順にソートする。
- 各 \(i = 0, 1, \ldots, N-1\) について以下を行う:
- 閾値 \(\text{threshold} = K - A_i\) を計算する。
- 配列 \(A\) の区間 \([i+1, N)\) において、\(A_j \geq \text{threshold}\) を満たす最小のインデックスを二分探索(
bisect_left)で求める。このインデックスを \(\text{idx}\) とする。 - 条件を満たすペアの個数は \(N - \text{idx}\) 個なので、これを答えに加算する。
- 最終的な合計を出力する。
bisect_left(A, threshold, i+1, N) は、探索範囲を \(i+1\) 以降に限定することで、\(i < j\) の条件を自然に満たし、同じペアを二重に数えることを防ぎます。
計算量
- 時間計算量: \(O(N \log N)\)(ソートに \(O(N \log N)\)、各要素に対する二分探索に \(O(\log N)\) × \(N\) 回)
- 空間計算量: \(O(N)\)(配列の格納)
実装のポイント
探索範囲の指定:
bisect_leftの第3・第4引数で探索範囲をi+1からNに限定することで、自分自身とのペアや重複カウントを防いでいます。得点の値が最大 \(10^9\) で合計が最大 \(2 \times 10^9\): Python は多倍長整数を扱えるのでオーバーフローの心配はありませんが、C++ などでは
long longを使う必要があります。答えの型: ペア数は最大で \(\frac{N(N-1)}{2} \approx 2 \times 10^{10}\) になりうるため、32ビット整数では不足する点に注意が必要です(Python では問題なし)。
ソースコード
import bisect
def main():
import sys
input_data = sys.stdin.read().split()
N = int(input_data[0])
K = int(input_data[1])
A = [int(input_data[i + 2]) for i in range(N)]
A.sort()
count = 0
for i in range(N):
# A[i] + A[j] >= K => A[j] >= K - A[i]
threshold = K - A[i]
# Find the leftmost index j in A where A[j] >= threshold, but j > i
idx = bisect.bisect_left(A, threshold, i + 1, N)
count += N - idx
print(count)
main()
この解説は claude4.6opus-thinking によって生成されました。
投稿日時:
最終更新: