CS Academy Expected Merge

リンク : https://csacademy.com/contest/archive/task/expected-merge/statement/

問題概要

マージソートアルゴリズムと同様の関数を考えた時に、数列を半分にすることを繰り返す。奇数長の場合、区切られる場所は一意に定まらない。そこで、一様にランダムで分割する場合、各indexのiに関して、関数でiを含む数列の区間の長さの合計の期待値を求めよ。

解説

下の図は、入力が5の場合を考えている。4が含む数列の長さの合計の期待値を求めるときに(3,4,5)と(4,5)を見ることは、(1,2,3)と(1,2)を見ることが同じであることがわかる。 f:id:akusyounin:20190216194915p:plain
そこでdp[i][j]を長さiの数列のj番目を含む数列の区間の長さの期待値とすると、次の関数に行くときのindexの変化のみ計算すればよい。しかし、これでは空間時間ともにO(n2)になると思うかもしれないが、n→n/2→n/4と変化するため間に存在する多くは巡らない。よってO(n logn)となる。配列も前もって静的に用意してしまうとMLEになってしまう。

LL n;
vector<double>dp[110000];
double func(int i, int j) {
    if (i == 1)return 1;
    if (dp[i].size() == 0)dp[i].resize(i+1);
    if (dp[i][j] != 0)return dp[i][j];
    double ret = 0;
    if (i % 2 == 0) {
        ret += func(i / 2, j % (i / 2));
    }
    else {
        if (j == (i + 1) / 2) {
            ret += func((i + 1) / 2, j);
            ret += func((i + 1) / 2, 1);
        }
        else {
            if (j > (i + 1) / 2) {
                ret += func(i / 2, j - (i+1) / 2);
                ret += func((i + 1) / 2, j - (i) / 2);
            }
            else {
                ret += func(i / 2, j);
                ret += func((i + 1) / 2, j);
            }
        }
        ret /= 2.0;
    }
    ret += i;
    return dp[i][j] = ret;
}
int main() {
    cin >> n;
    rep(i, n) {
        if (i)cout << " ";
        printf("%.15f", func(n, i+1));
    }
    cout << endl;
    return 0;
}