Official

E - Simultaneous Kagamimochi Editorial by MMNMM

より高速な解法

最悪時間計算量を \(\Theta(N)\) 時間とする \(2\) つの方針について解説します。


餅の列を先頭 \(\left\lfloor\dfrac N2\right\rfloor\) 個とそれ以外に分けます。

後ろの列を先頭から順に見て、それぞれの餅ごとに先頭の列のうち残っている中で最小の餅(存在すれば)と組み合わせて鏡餅を作れるなら作るアルゴリズムを考えます。

このアルゴリズムで作られる鏡餅の個数が求める最大値です。 このことは、\(K\) 個の鏡餅を作ることができるなら先頭 \(K\) 個を上側として使い、後半 \(\left\lceil\dfrac N2\right\rceil\) 個のうち \(K\) 個を下側として使う鏡餅の作り方が存在することと、そのような作り方で先頭 \(k\) 個に対応する鏡餅を作り終えた時点での後半の鏡餅の残り方として最良のものをこのアルゴリズムが与えることから確認できます。

実装例は以下のようになります。

#include <iostream>
#include <deque>

int main() {
    using namespace std;
    unsigned N;
    cin >> N;

    // 餅列の先頭側 floor(N / 2) 個
    deque<unsigned> A(N / 2);
    for (auto&& a : A)
        cin >> a;

    unsigned ans{};

    for (unsigned i{N / 2}; i < N; ++i) {
        unsigned a;
        cin >> a;
        // もし残っているうち最小の餅と鏡餅を作れるなら作る
        if (A.front() * 2 <= a) {
            ++ans;
            A.pop_front();
        }
    }

    cout << ans << endl;

    return 0;
}

便宜上、列の後ろに無限の大きさの餅が無限に並んでいることとします。 ここで、先頭 \(K\) 項を上段の餅としてすべて使って \(K\) 個鏡餅を作る作り方における、下段に使われる鏡餅のインデックスの最大値の最小値について考えます(つまり、\(K\) 個鏡餅を作るのに少なくとも何番目までの餅を使う必要があるかについて考えます)。

\(B _ i\coloneqq i\) 番目の餅を上段の餅として使うときの、下段の餅としてありえる最小のインデックス \((1\leq i\leq N)\) を求めることは \(\Theta(N)\) 時間でできます(C 問題と同様の尺取り法を用いればよいです)。 すると、\(B _ i\) を用いて上の値は \(\displaystyle K+\max\left\lbrace\max _ {1\leq i\leq K}\lbrace B _ i-i\rbrace,K\right\rbrace\) と表せます。

あとは、これが \(N\) 以下であるような最大の \(K\) を求めればよいです。 これは \(B _ i\) を求めながら \(K\) を小さいほうから調べていけばよいです。

実装例は以下のようになります(ここまでの説明は 1-indexed で行われていましたが、実装例では 0-indexed になっていることに注意してください)。

#include <iostream>
#include <vector>

int main() {
    using namespace std;
    unsigned N;
    cin >> N;

    vector<unsigned> A(N);
    for (auto&& a : A)
        cin >> a;

    for (unsigned i = 0, j = 0, d = 0; i < N; ++i) {
        // j := i 番目の餅を乗せられる最小の餅の番号
        while (j < N && A[i] * 2 > A[j]) ++j;
        // d := 上下の餅の番号の差の最大値
        d = max(d, j - i);
        
        // 後ろがはみ出したら
        if (i + max(d, i + 1) >= N) {
            // 直前の値が答え
            cout << i << endl;
            return 0;
        }
    }
    return 0;
}

posted:
last update: