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\) を表すこととします。
- 操作の直後、\(i=1,2,\ldots,N\) のすべてについて \(P(i)\) が成り立つ
- 操作は、次のように言い換えることができる。
- \(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:
