E - 整列の手間 / The Effort of Sorting 解説
by
MMNMM
隣接する \(2\) 要素 \(P _ i,P _ {i+1}\) を入れ替えたとき、転倒数がどのように変化するかについて考えます。
\(P _ i,P _ {i+1}\) のどちらでもない要素が含まれるペアについては、添え字と値の大小関係は操作の前後で変化しません(ここで、添え字ではなく列の値で組を見ていることに注意してください。たとえば、操作前の組 \((k,i)\) には操作後の組 \((k,i+1)\) を対応させています)。 よって、\(P _ i\lt P _ {i+1}\) なら、操作の前後で転倒数は \(1\) 増加します。\(P _ i\gt P _ {i+1}\) なら、操作の前後で転倒数は \(1\) 減少します。
すべての組が \(P _ i\lt P _ {i+1}\) を満たすとき、かつそのときに限り転倒数は \(0\) です。 同様に、すべての組が \(P _ i\gt P _ {i+1}\) を満たすとき、かつそのときに限り転倒数は \(\dfrac{N(N-1)}2\) です。
これらの事実から、はじめの転倒数が \(I\) の状態からちょうど \(K\) 回操作を行ったときの転倒数 \(J\) は次の条件を満たすことがわかります。
- \(0\le J\le\dfrac{N(N-1)}2\)
- \(I-K\le J\le I+K\)
- \(I+K\equiv J\pmod 2\)
逆に、これらの条件を満たす \(J\) について、ちょうど \(K\) 回の操作で転倒数を \(J\) にできることもわかります。
証明
次のような手順を考えます。
- 現在の転倒数が \(J\) より大きいなら、\(P _ i\lt P _ {i+1}\) なる組に対して操作を行う。
- 現在の転倒数が \(J\) より小さいなら、\(P _ i\gt P _ {i+1}\) なる組に対して操作を行う。
- 現在の転倒数が \(J\) なら、適当な組に対して操作を行う。
一番目の条件から、上の手順が常に行えることがわかります。
この手順を行ったとき、はじめて \(J\) に到達するまでに \(|I-J|\) 回の操作を行います。 二番目の条件から、\(K\) 回操作を行うまでに \(J\) に到達することがわかります。
一度 \(J\) に到達したら、そのあとの転倒数 \(x\) は \(|x-J|\le 1\) を満たします。 三番目の条件から、ちょうど \(K\) 回の操作を行ったあとの転倒数 \(x\) の偶奇は \(J\) と等しいので、\(x=J\) が示されました。
よって、上の条件を満たす \(J\) の最小値を求めればよいです。
\(0,1,I-K\) のいずれかが求める最小値となるので、それらを全部試せばこの問題を解くことができます。
はじめの転倒数 \(I\) を求めるのがボトルネックとなり、時間計算量は \(O(N\log N)\) などとなります。
実装例は以下のようになります。
#include <iostream>
#include <atcoder/fenwicktree>
using namespace std;
int main() {
int N;
long K;
cin >> N >> K;
# 転倒数を求める
atcoder::fenwick_tree<int> bit(N);
long inversion = 0;
for (int i = 0; i < N; ++i) {
int A;
cin >> A;
inversion += bit.sum(A, N);
bit.add(A - 1, 1);
}
if (inversion >= K) {
cout << inversion - K << endl;
} else {
cout << (inversion + K) % 2 << endl;
}
return 0;
}
from atcoder.fenwicktree import FenwickTree
N, K = map(int, input().split())
# 転倒数を求める
bit = FenwickTree(N)
inversion = 0
for A in map(int, input().split()):
inversion += bit.sum(A, N)
bit.add(A - 1,1)
if inversion >= K:
print(inversion - K)
else:
print((inversion + K) % 2)
投稿日時:
最終更新:
