Official

E - Water Tank Editorial by MMNMM


\(n=1,2,\ldots,N\) について、\(A _ n\) がはじめて正になるような操作の直前に \(A _ i\ (i\lt n)\) の値がどのようになっているかを考えます。

論証による導出

本問の操作について、次の \(2\) つが成り立つことを示します。

\(1\leq i\leq N\) なる整数 \(i\) に対して、条件 \(P(i)\) で \(A _ {i-1}\leq\max\lbrace H _ i,A _ i\rbrace\) を表すこととします。

  1. 操作の直後、\(i=1,2,\ldots,N\) のすべてについて \(P(i)\) が成り立つ
  2. 操作は、次のように言い換えることができる。
    • \(i _ {\mathrm C}\) を \(A _ {i-1}\lt\max\lbrace H _ i,A _ i\rbrace\) が成り立つ最小の \(i\ (1\leq i\leq N)\)(存在しなければ \(N+1\,\))と定める。
    • \(A _ {i _ {\mathrm C}-1}\) の値を \(1\) 増やす。

まず、\(1\implies2\) を示します。

証明

\(P(i)\) は \(\neg(A _ {i-1}\gt A _ i\wedge A _ {i-1}\gt H _ i)\) と同値であることに注意します。

操作が始まる前、\(1\leq i\lt i _ {\mathrm C}\) においては \(A _ {i-1}=\max\lbrace H _ i,A _ i\rbrace\) が成り立っています。 \(i\) に対する \(2\) 番の操作を行う時点では、\(1\) 番の操作もしくは \(i-1\) に対する \(2\) 番の操作によって \(A _ {i-1}\) の値が \(1\) 増加しているので、\(A _ {i-1}\gt A _ i\) および \(A _ {i-1}\gt H _ i\) が成り立ちます。

\(1\) 番の操作と \(1\leq i\lt i _ {\mathrm C}\) に対する \(2\) 番の操作が行われた直後、\(A _ {i _ {\mathrm C}-1}\) の値だけ \(1\) 増加しています。 \(i _ {\mathrm C}\) の取り方から、\(i=i _ {\mathrm C}\) に対する \(2\) 番の操作を行う直前にも \(P(i _ {\mathrm C})\) が成り立っています。 よって、\(A _ {i _ {\mathrm C}}\) の値は変化しません。 仮定よりどの \(i _ {\mathrm C}\lt i\) に対しても \(P(i)\) が成り立っているので、\(i _ C\lt i\) に対する \(2\) 番の操作の条件が満たされることはありません。

よって、\(1\implies2\) が示されました。

次に、\(1\) を示します。 操作回数に対する帰納法を行います。

証明

はじめ、\(A _ 0=A _ 1=\dotsb=A _ N=0\) なので、成り立っています。

条件が成り立っている状態から \(1\) 回だけ操作を行っても条件が成り立つことを示します。

上の証明から、条件が成り立っている状態から操作を行うと、\(A _ {i-1}\lt\max\lbrace H _ i,A _ i\rbrace\) が成り立つ最小の \(i\)(存在しなければ \(N+1\,\))に対する \(A _ {i-1}\) の値のみが \(1\) だけ増加します。

よって、\(i\neq i _ {\mathrm C}-1,i _ {\mathrm C}\) に対しては \(A _ {i-1},A _ i\) の値は変化せず、\(P(i)\) が変わらず成り立ちます。

\(i=i _ {\mathrm C}-1\) に対しては、\(A _ i\) の値が増加するため \(\max\lbrace H _ i,A _ i\rbrace\) が減少することはなく、\(P(i _ {\mathrm C}-1)\) が成り立ちます。
\(i _ {\mathrm C}\leq N\) のとき、\(i=i _ {\mathrm C}\) に対しては、\(i _ {\mathrm C}\) の取り方から操作が始まる前には \(A _ {i-1}\lt\max\lbrace H _ i,A _ i\rbrace\) が成り立っているため、\(A _ {i-1}\) の値を \(1\) 増やしても \(P(i _ {\mathrm C})\) が成り立ちます。

以上より、\(1\) が示されました。

\(2\) の言い換えにより、\(A _ n\) がはじめて正になるような操作では、\(i _ {\mathrm C}=n\) となる必要があることがわかります。 つまり、この操作の直前において、すべての \(1\leq i\lt n\) に対して \(A _ {i-1}=\max\lbrace H _ i,A _ i\rbrace\) が成り立っています。

ここから \(i=n-1,n-2,\dotsc,1,0\) の順に \(A _ i\) の値を定めることができ、\(\displaystyle A _ i=\max _ {i\lt j\leq n}H _ j\ (0\leq i\lt n)\) と書けることがわかります。

丁寧に条件を考察したり実験して観察をしたりすることで、\(A _ n\) がはじめて正になるような操作の直前には \(\displaystyle A _ i=\max _ {i\lt j\leq n}H _ j\ (0\leq i\lt n)\) が成り立っていることがわかります。

はじめ \(\displaystyle\sum _ {i=0} ^ NA _ i=0\) であり、操作を一度行うごとに \(\displaystyle\sum _ {i=0} ^ NA _ i\) の値は \(1\) ずつ増加するので、\[\sum _ {i=0} ^ NA _ i=1+\sum _ {i=0} ^ {n-1}\max _ {i\lt j\leq n}H _ j\] の値が \(i=n\) に対する答えになります。

\(n\) が増えるとき \(\displaystyle X _ i\coloneqq\max _ {i\lt j\leq n}H _ j\) がどのように変化するかを考えることで、次のような問題に言い換えることができます。

長さ \(N\) の数列 \(X=(X _ 1,X _ 2,\dotsc,X _ N)\) がある。はじめ、\(X _ 1=X _ 2=\dotsb=X _ N=0\) である。 \(i=1,2,\ldots,N\) に対して順に次の操作を行う。

  • \(1\leq j\leq i\) に対し、\(X _ j\leftarrow\max\lbrace X _ j,H _ i\rbrace\) と更新する。

それぞれの操作の直後の \(\displaystyle1+\sum _ {i=1} ^ NX _ i\) を求めよ。

この問題を解く方針はいくつかあります。

  • stack を用いて \(\bigl(H _ i,(X _ j=H _ i\) となる \(j\) の個数\()\bigr)\) となる列を管理する
    • \(X _ j\leftarrow\max\lbrace X _ j,H _ i\rbrace\) では、stack の先頭の組 \((v,c)\) に対して \(v\leq H _ i\) である限り先頭を取り出すことを繰り返し、\(\bigl(H _ i,(\)取り出した \(c\) の合計\({}+1)\bigr)\) を追加する
    • \(\sum X _ i\) も stack の操作と同時に更新
  • 遅延セグメント木を用いて区間更新・区間和クエリにする
    • \(X\) が単調減少列になるので、更新すべき区間は二分探索で求めることができる
  • segment tree beats を用いて区間 chmax ・区間和クエリを処理する

\(1\) つめの方針では時間・空間計算量が \(\Theta(N)\) 、\(2\) つめと \(3\) つめの方針では \(\Theta(N\log N)\) 時間と \(\Theta(N)\) 空間になります。

実装例は以下のようになります。 この実装例では \(1\) つめの方針で解いています。

#include <iostream>
#include <utility>
#include <vector>

int main() {
    using namespace std;
    unsigned N;
    cin >> N;
    // 高さについて単調減少になるように (高さ, 個数) を管理する
    vector<pair<unsigned, unsigned>> rectangles;

    // 1 + ∑ 高さ × 個数
    unsigned long ans = 1;

    for (unsigned i = 0, H; i < N; ++i) {
        cin >> H;
        // 高さは H 、はじめ個数は 1
        unsigned count = 1;

        // H より低いものがある限り更新
        while (!empty(rectangles) && rectangles.back().first <= H) {
            const auto &[h, c] = rectangles.back();
            // 合計から h × c を引いて
            ans -= static_cast<unsigned long>(h) * c;
            // 個数を増やして
            count += c;
            // 列から取り除く
            rectangles.pop_back();
        }

        // 合計に H × count を足す
        ans += static_cast<unsigned long>(H) * count;
        rectangles.emplace_back(H, count);
        cout << ans << " ";
    }
    cout << endl;
    return 0;
}

posted:
last update: