claw88のブログ

競技プログラミングとかC#とか。

Kyoto University Programming Contest 2018 (KUPC2018) : E - 転倒数

かなり険しかった。
https://beta.atcoder.jp/contests/kupc2018/tasks/kupc2018_e

前提

Chokudai SpeedRun 001 : J - 転倒数
Chokudai SpeedRun 001 : K - 辞書順で何番目?
この2題によって、1つのBITで順列の転倒数と辞書順で何番目かを同時に求めることができるようになった。

問題

順列Aが与えられる。その順列より辞書順で小さいか等しいものの転倒数の総和を求めよ。

解法

公式解説
※以下ではすべて1-indexedだが、下記のソースコードでは順列が0-indexedであることに注意
まず、n桁の順列の転倒数の総和を求める。これをP[n]とする。これは考察してDPをするか、実験してOEISすることで
P[n] = n! * n * (n - 1) / 4
とわかる。

辞書順で何番目?のときと同じように、i番目で初めて順列Aよりも小さくなる順列たちの転倒数の総和を求めていく。
このとき、順列Aと等しい区間[1,i-1]i、後で決める区間 [i+1,N]に分けて考え、これをゾーン1、ゾーン2、ゾーン3とする。

iに対して、
①ゾーン3の転倒数の総和
②ゾーン2とゾーン3をマージするための転倒数の総和
③ゾーン1とゾーン23をマージするための総和
 ansに足していけばよい。

ゾーン2の値として選ぶことができる要素の数cntをBITから計算する。

①ゾーン3をソートするための転倒数の総和は、ゾーン3の並べ方は自由なので、
cnt * P[N - i]
である。
②ゾーン2とゾーン3のマージコストを考える。
ゾーン2の候補の中でj番目に小さい値は、ゾーン2とゾーン3の中でも j番目に小さい。
すなわち、 j -1回の転倒数が発生する。ゾーン3の並べ方は(N - i)!通りであるから、
これをすべてのゾーン2の候補について足し合わせると
 (cnt - 1) * cnt / 2 * (N - i)!
である。
③ゾーン1とゾーン23のマージコストを考える
必要な転倒数はゾーン1のソートコストと、ゾーン1とゾーン23のマージコストである。
ゾーン1のソートコストinv1は、BITから計算できた。
マージコスmargeは当たり前だが、ゾーン1の各要素よりも小さいゾーン1にない(ゾーン23にある)要素の数の和である。

これはBITを使って更新していくことができる。
cntはまさにA[i]より小さいゾーン1にない要素の数であるからmargeに加える。
ゾーン1にA[i]より大きい数があった場合、A[i]がゾーン1としてソートされることでマージコストは減る。
margeから\text{BIT}.Sum(i+1,N)を引く。

ゾーン2とゾーン3の並べ方はcnt*(N-i)!通りであるから、
 (inv1 + marge) * cnt * (N - i)!
である。

④最後に順列 A自身の転倒数を加えておしまい。

実装

ACコード

void solve()
{
    int N = cin.nextint;
    var F = new ModInt[N + 1];
    var P = new ModInt[N + 1];
    F[0] = 1;
    var f = ModInt.Pow(4, mod - 2);

    for (int i = 1; i <= N; i++)
    {
        F[i] = F[i - 1] * i;
        P[i] = F[i] * i * (i - 1) * f;
    }

    var BIT = new FenwickTree(N); //1-indexed, sumは閉区間
    ModInt inv1 = 0; //ゾーン1の転倒数       
    ModInt marge = 0; //ゾーン1と23のマージコスト
    ModInt ans = 0;

    for (int i = 0; i < N; i++)
    {
        int p = cin.nextint;
        BIT.Add(p, 1);
        var cnt = p - BIT[p];//ゾーン2の候補数

        //ゾーン3の転倒数
        var inv3 = P[N - i - 1];

        ans += cnt * inv3; //ゾーン3合計
        ans += cnt * (cnt - 1) / 2 % mod * F[N - i - 1]; //ゾーン2と3のマージ
        ans += cnt * (inv1 + marge) * F[N - i - 1]; //ゾーン1と23のマージ


        inv1 += BIT[p + 1, N];

        marge += cnt;
        marge -= BIT[p + 1, N];

    }

    WriteLine(ans + inv1);
}

まとめ

一般人は2時間では解けないのでなるほど5時間コンテストらしい問題だなあと思う。(考え始めてから6時間はかかりましたが)