Official

E - 整列の手間 / The Effort of Sorting Editorial by admin

claude4.8opus-high

概要

順列に対して「隣接する2要素の交換」をちょうど \(K\) 回行ったときの、転倒数の最小値を求める問題です。隣接交換が転倒数を \(\pm 1\) だけ変化させる性質を使うと、初期転倒数 \(T\) だけから答えを計算できます。

考察

重要な観察:隣接交換は転倒数を1だけ変える

隣り合う2人 \(P_i\)\(P_{i+1}\) を交換すると、この2要素以外との位置関係は一切変わりません。変化するのはこの2要素の大小関係だけです。

  • \(P_i > P_{i+1}\)(逆順)のとき交換すると、転倒が1つ解消され、転倒数は \(-1\)
  • \(P_i < P_{i+1}\)(正順)のとき交換すると、転倒が1つ増え、転倒数は \(+1\)

つまり、1回の操作で転倒数はちょうど \(1\) だけ増えるか減るかします。操作のたびに転倒数の偶奇(パリティ)が反転することがポイントです。

操作回数 \(K\) ごとの場合分け

初期の転倒数を \(T\) とします。

ケース1:\(K \le T\) のとき

転倒している箇所を選んで交換すれば、毎回 \(1\) ずつ転倒数を減らせます。\(K\) 回すべてを「減らす操作」に使えるので、到達できる最小値は $\(T - K\)$ となります。

ケース2:\(K > T\) のとき

まず \(T\) 回の操作で転倒数を \(0\)(完全に昇順)にできます。しかし操作は「ちょうど \(K\) 回」行う必要があるため、残り \(K - T\) 回の操作を消化しなければなりません。

転倒数が \(0\) の状態からは、どの隣接交換も必ず転倒数を \(+1\) にします。そこで、適当な2要素を「交換 → 元に戻す」を繰り返すと、転倒数は \(0 \to 1 \to 0 \to 1 \to \cdots\) と振動します。

したがって残り \(K - T\) 回の操作後の転倒数は、その偶奇で決まります。 - \(K - T\) が偶数なら最小値は \(0\) - \(K - T\) が奇数なら最小値は \(1\)

これは \((K - T) \bmod 2\) とまとめられます。

素朴なアプローチの問題点

「実際に操作をシミュレーションする」方法では、\(K\) が最大 \(10^{18}\) にもなるため到底間に合いません。上記の考察により、答えは初期転倒数 \(T\) さえ求めれば \(O(1)\) で確定します。

アルゴリズム

やるべきことは「初期転倒数 \(T\) を求める」ことだけです。転倒数は BIT(Binary Indexed Tree / フェニック木) を使って効率的に数えられます。

具体的な手順(配列を右から左へ走査): 1. BIT を「これまでに見た値の集合」として管理する。 2. 位置 \(i\) の値 \(v = P_i\) について、すでに右側に現れた値のうち \(v\) より小さいものの個数を query(v-1) で求め、\(T\) に加算する。これは「\(i < j\) かつ \(P_i > P_j\)」となる組のうち \(i\) を固定したときの個数に対応します。 3. update(v) で値 \(v\) を BIT に登録する。

走査が終われば \(T\) が転倒数になります。あとは上記の場合分けで答えを出力します。

計算量

  • 時間計算量: \(O(N \log N)\)(BIT による転倒数計算が支配的。場合分けは \(O(1)\)
  • 空間計算量: \(O(N)\)(BIT の配列)

実装のポイント

  • \(K\) は最大 \(10^{18}\) と非常に大きいので、言語によっては64bit整数(Pythonでは多倍長整数なので問題なし)を意識する必要があります。

  • 値が \(1\) から \(N\) の順列であることを利用し、BIT のインデックスに値そのものを使えます。

  • 場合分けは「\(K \le T\) なら \(T - K\)」「それ以外は \((K - T) \bmod 2\)」という非常にシンプルな式に帰着できる点が本問の核心です。

    ソースコード

import sys

def main():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    K = int(input_data[idx]); idx += 1
    P = [int(input_data[idx + i]) for i in range(N)]
    
    # 転倒数を BIT で計算
    bit = [0] * (N + 1)
    
    def update(i):
        while i <= N:
            bit[i] += 1
            i += i & (-i)
    
    def query(i):
        s = 0
        while i > 0:
            s += bit[i]
            i -= i & (-i)
        return s
    
    T = 0
    # 右から左へ
    for i in range(N - 1, -1, -1):
        v = P[i]
        # v より小さい値で既に右側にあるものの数
        T += query(v - 1)
        update(v)
    
    if K <= T:
        ans = T - K
    else:
        ans = (K - T) % 2
    
    sys.stdout.write(str(ans) + "\n")

main()

この解説は claude4.8opus-high によって生成されました。

posted:
last update: