E - 隣接スワップで転倒数を減らせ / Reduce Inversions with Adjacent Swaps 解説 by admin
Gemini 3.0 Flash (Thinking)概要
この問題は、与えられた順列 \(P\) に対して、隣接する要素の入れ替え(スワップ)を最大 \(K\) 回行い、最終的な転倒数を最小化する問題です。
考察
転倒数と隣接スワップの関係
この問題を解くための最大の鍵は、「隣接する 2 要素のスワップ 1 回につき、転倒数はちょうど 1 増えるか 1 減る」という性質です。
- \(P_i > P_{i+1}\) である隣接要素をスワップすると、転倒数は 1 減少します。
- \(P_i < P_{i+1}\) である隣接要素をスワップすると、転倒数は 1 増加します。
目標は転倒数を最小化することなので、可能な限り「転倒数が 1 減るスワップ」を繰り返すべきです。
最適な戦略
転倒数が \(0\) より大きい(=昇順に並んでいない)とき、必ず \(P_i > P_{i+1}\) となる箇所が存在します。したがって、最大 \(K\) 回のスワップが許されている場合、以下のようになります。
- 元の転倒数を \(I\) とします。
- \(K \geq I\) ならば、すべての転倒を解消して転倒数を \(0\) にできます。
- \(K < I\) ならば、スワップするたびに転倒数を 1 ずつ減らすことで、最小値を \(I - K\) にできます。
結局、この問題は「初期状態の転倒数を効率よく求める」問題に帰着されます。
転倒数の計算方法
転倒数の定義は \(1 \leq i < j \leq N\) かつ \(P_i > P_j\) となる組の数です。 素直に二重ループで数えると \(O(N^2)\) かかり、\(N = 2 \times 10^5\) の制約下では実行時間制限に間に合いません。 そこで、フェニック木(Binary Indexed Tree, BIT)を用いることで、\(O(N \log N)\) で計算することが可能になります。
アルゴリズム
- BITの準備: 長さ \(N+1\) の BIT を用意します。
- 転倒数の算出: 順列 \(P\) の要素を先頭から順に見ていき、以下の処理を行います。
- 現在の要素 \(P_i\) よりも「既に処理された(=左側にある)」かつ「値が大きい」要素の数を数えます。
- これは、「既に処理された要素の総数」から「既に処理された \(P_i\) 以下の要素の数」を引くことで求められます。
- BIT を使って \(P_i\) 以下の要素の数を \(O(\log N)\) で取得し、転倒数に加算します。
- BIT の \(P_i\) 番目に \(1\) を加算し、出現済みであることを記録します。
- 答えの出力:
max(0, 初期転倒数 - K)を出力します。
計算量
- 時間計算量: \(O(N \log N)\)
- 順列の各要素に対して、BIT の更新とクエリ(ともに \(O(\log N)\))を行うためです。
- 空間計算量: \(O(N)\)
- 入力配列の保持および BIT の管理に \(O(N)\) のメモリを使用します。
実装のポイント
高速な入出力: \(N\) が大きいため、Python では
sys.stdin.read().split()などを用いて一括で入力を読み込むと高速です。1-indexed: BIT は通常 1-indexed で実装されます。今回のカード番号 \(1 \sim N\) とそのまま対応させることができるため、実装がスムーズになります。
\(K\) の大きさ: \(K\) は最大 \(10^{18}\) と非常に大きくなる可能性があるため、必ず
inv_count - kが負にならないようmax(0, ...)の処理(またはif文による判定)が必要です。ソースコード
import sys
def solve():
# Use sys.stdin.read().split() for fast reading of all input at once.
# This is more efficient than calling input() multiple times for large N.
input_data = sys.stdin.read().split()
if not input_data:
return
# N: Number of cards
# K: Maximum number of adjacent swaps allowed
n = int(input_data[0])
k = int(input_data[1])
# P: The current permutation of the cards (1 to N)
p = list(map(int, input_data[2:]))
# Fenwick Tree (Binary Indexed Tree) to count inversions in O(N log N).
# The BIT is 1-indexed, which aligns perfectly with card numbers 1 to N.
bit = [0] * (n + 1)
inv_count = 0
# Iterate through the permutation and calculate the initial number of inversions.
# For each element p[i], we count how many elements already processed are greater than it.
for i, val in enumerate(p):
# Query: Calculate the count of elements <= val already seen.
# This loop traverses the BIT structure in O(log N).
s = 0
idx = val
while idx > 0:
s += bit[idx]
idx -= idx & -idx
# 'i' is the total number of elements processed before the current element.
# 's' is the number of those elements that are less than or equal to 'val'.
# Therefore, 'i - s' is the number of elements processed that are greater than 'val'.
inv_count += (i - s)
# Update: Add 1 to the BIT at index 'val' to mark it as seen.
# This loop also runs in O(log N).
idx = val
while idx <= n:
bit[idx] += 1
idx += idx & -idx
# Each adjacent swap can reduce the inversion count by at most 1.
# To minimize the inversion count, we perform swaps on pairs (P_i, P_{i+1})
# where P_i > P_{i+1}, which reduces the total inversion count by exactly 1.
# If the total inversions 'inv_count' is less than or equal to K, we can reach 0.
# Otherwise, we can reduce the inversion count to 'inv_count - K'.
result = inv_count - k
if result < 0:
result = 0
# Print the minimum possible inversion count.
print(result)
if __name__ == '__main__':
solve()
この解説は gemini-3-flash-thinking によって生成されました。
投稿日時:
最終更新: