E - マラソン大会 / Marathon 解説 by admin
Gemini 3.0 FlashOverview
Given the initial positions \(P_i\) and velocities \(V_i\) of \(N\) runners, the problem asks to find the total number of “overtakes” that will occur in the future. An overtake occurs only when a runner who is initially behind another runner has a greater velocity than the runner ahead.
Analysis
Conditions for an Overtake to Occur
Consider two runners \(i\) and \(j\), where at time \(0\), runner \(i\) is behind runner \(j\) (\(P_i < P_j\)). The condition for runner \(i\) to overtake runner \(j\) at some time \(t > 0\) is that runner \(i\)’s velocity is greater than runner \(j\)’s velocity, i.e., \(V_i > V_j\).
If \(V_i < V_j\), the distance between the two increases over time, so an overtake will never occur.
Reformulating the Problem
Let’s sort all runners in ascending order of their initial positions \(P_i\). Then, counting the number of overtakes is equivalent to counting, in the resulting sequence of velocities, the number of elements to the left (with smaller initial positions) that have a larger value (higher velocity).
This reduces to the well-known problem in algorithms called the “Inversion Number”.
Naive Approach and Its Limitation
If we check whether an overtake occurs for every pair \((i, j)\), the time complexity is \(O(N^2)\). Given the constraint \(N \le 2 \times 10^5\), this will not finish within the time limit. A more efficient \(O(N \log N)\) algorithm is needed.
Algorithm
We compute the answer using the following steps:
- Sort: Sort the runners in ascending order of their initial positions \(P_i\).
- Coordinate Compression: Since velocity values \(V_i\) can be as large as \(10^9\), they cannot be directly used as array indices. Since only the relative ordering of velocities matters, we convert the velocities into ranks from \(1\) to \(N\) (coordinate compression).
- Inversion Count Calculation: Using a Binary Indexed Tree (BIT) or segment tree, process the sorted velocity sequence from left to right and count inversions.
- Let \(r\) be the rank of the current runner’s velocity.
- Using the BIT, find the number of previously processed runners (those with smaller initial positions) whose velocity rank is greater than \(r\), and add it to the answer.
- Register the current velocity rank \(r\) in the BIT.
Complexity
- Time Complexity: \(O(N \log N)\)
- Sorting the runners takes \(O(N \log N)\).
- Coordinate compression of velocities takes \(O(N \log N)\).
- Computing the inversion count using a BIT takes \(O(N \log N)\).
- Space Complexity: \(O(N)\)
- Memory proportional to \(N\) is used for storing runner data and managing the BIT.
Implementation Notes
Fast I/O: Since \(N\) can be large, in Python it is faster to read all input at once using
sys.stdin.read().split()or similar methods.Counting Inversions: When using a BIT to find “the number of elements greater than \(r\)”, it is common to compute it as “total count - count of elements less than or equal to \(r\)”.
Coordinate Compression Dictionary: By using a dictionary (hash map) such as
rank_map = {v: i + 1 for i, v in enumerate(v_sorted)}, the rank of each velocity can be looked up in \(O(1)\).Source Code
import sys
def solve():
# 高速な入出力のために、すべての入力を一度に読み込み、空白で分割してリスト化します。
# sys.stdin.read().split() は、空白、タブ、改行をすべて区切り文字として扱います。
input_data = sys.stdin.read().split()
if not input_data:
return
# 最初のエントリはランナーの人数 N です。
N = int(input_data[0])
if N == 0:
print(0)
return
# P_i と V_i を効率的にパースしてペアにします。
# input_data[1:2*N+1:2] は P_1, P_2, ..., P_N を抽出し、
# input_data[2:2*N+1:2] は V_1, V_2, ..., V_N を抽出します。
# map(int, ...) で整数に変換し、zip で (位置, 速度) のタプルのリストを作成します。
runners = list(zip(map(int, input_data[1:2*N+1:2]), map(int, input_data[2:2*N+1:2])))
# ランナーを初期位置 P_i の昇順でソートします。
# すべての P_i は異なるため、一意な順序が得られます。
runners.sort()
# 位置の順にソートされた状態での速度のリストを抽出します。
# 追い抜き回数を求めることは、この速度のリストにおける「転倒数」を求めることと同義です。
# なぜなら、P_i < P_j かつ V_i > V_j のときに限り、時刻 t > 0 で追い抜きが発生するからです。
vels = [r[1] for r in runners]
# 速度の値が大きいため(最大 10^9)、座標圧縮を行います。
# 各速度を、全速度の中での順位(1 から N)に変換します。
v_sorted = sorted(vels)
rank_map = {v: i + 1 for i, v in enumerate(v_sorted)}
ranks = [rank_map[v] for v in vels]
# 転倒数を計算するために Binary Indexed Tree (Fenwick Tree) を使用します。
# bit[i] は、これまでに処理した要素のうち、順位が i であるものの個数を管理します。
bit = [0] * (N + 1)
total_overtakes = 0
# 各速度の順位を順番に処理し、それより大きい順位が既にいくつ現れたかをカウントします。
for i, r in enumerate(ranks):
# BITのクエリ:現在までに処理した要素のうち、順位が r 以下のものの個数 s を求めます。
s = 0
idx = r
while idx > 0:
s += bit[idx]
idx -= idx & (-idx)
# 現在までに処理した要素の総数は i 個です。
# そのうち順位が r 以下のものが s 個なので、r より大きいものは (i - s) 個です。
# これが、現在処理しているランナーよりも「前にいて遅い」ランナーの数になります。
total_overtakes += (i - s)
# BITの更新:現在のランナーの順位 r を BIT に追加します。
idx = r
while idx <= N:
bit[idx] += 1
idx += idx & (-idx)
# 最終的な追い抜きの総回数を出力します。
sys.stdout.write(str(total_overtakes) + '\n')
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3-flash-preview.
投稿日時:
最終更新: