G - ΣШX Editorial by ngtkana


終端 \(r\) による場合分け

\(r \in [0, N[\) に対して \(f(r)\) を次のように定義します。

\[ f(r) = \sum _ { 0 \le l \lt r } \mathrm{mex} (A[l, r[) \]

このとき答えは \(\sum_ {r = 1} ^ N f(r)\) になります。

\(f(r)\) を式変形する

すると、公式解説同様の考察により、\(f(r)\) が次のように表せます。

\[ \begin{aligned} f(r) &= \sum _ {i = 0} ^ {r - 1} \mathrm{mex}(A[i, r[) \\ &= \sum _ {i = 0} ^ {r - 1} \sum_{x=0}^N \big [ [0, x[ \ \subseteq A[i, r[ \big]\\ &= \sum _ {x = 0} ^ N \min _ { 0 \le x' \le x } b_ {x'} \end{aligned} \]

(ただし \(b _ x = \min \left \lbrace i \in [0, n[ \ \middle | x \not \in A[i, n[ \right \rbrace\) であり、命題 \(P\) に対して \(P\) が真ならば \([P] = 1\)、偽ならば \([P] = 0\) であるとします。)

Historical minimum で表現する

\((b_x)_{x=0}^N\) の hisotrical (strict) minimum positions を \(0 = i_0 < i_1 < \dots < i_{j-1} < i_k\) すると、\(f(r)\) は次のように書けます。

\[ f(r) = \sum _ {j = 1} ^ k (i_j - i_{j-1}) \cdot b_{i_{j-1}} \]

\(b_x\) を管理して \(f(r)\) を計算していきましょう

\(f(r) \ (r \in [1, N])\)\(r\) の大きい順に計算することを目指します。この順で見るとと \(b_x\) が時系列に関して単調減少になるため、償却更新箇所数 \(O(1)\) (実際には \(2\) 以下) のコストで historical minimum と \(f(r)\) を管理・更新することができます。

更新箇所の特定と更新は、いわゆる map (C++ の std::map, Rust の BTreeMap など) でできて、これにより全体 \(O(N \log N)\) 時間で計算できます。またより速い predecessor データ構造 (\(64\)-分木、van Emde Boas 木、Y-fast Trie など) で管理することで、\(O(N \log N / \log w)\) 時間、または \(O(N \log \log N)\) 時間で計算できます。

実装例

use proconio::input;
use std::collections::BTreeMap;

fn main() {
    input! {
        n: usize,
        a: [usize; n],
    }
    let mut b = vec![0; n + 1];
    let mut prev = vec![usize::MAX; n];
    for (i, &a) in a.iter().enumerate() {
        prev[i] = b[a];
        b[a] = i + 1;
    }
    let mut last = 0;
    let mut current = 0;
    let mut historical_min = BTreeMap::new();
    historical_min.insert(last, b[last]);
    for (x, &y) in b.iter().enumerate() {
        if b[last] > y {
            historical_min.insert(x, y);
            current += (x - last) * b[last];
            last = x;
        }
    }
    let mut ans = current;
    for (i, &x) in a.iter().enumerate().rev() {
        let (&first_x, &first_y) = historical_min.range(..=x).next_back().unwrap();
        let mut last_x = first_x;
        let mut last_y = first_y;
        let y = prev[i];
        b[x] = y;
        if y < last_y {
            loop {
                let (&k, &v) = historical_min.range(x + 1..).next().unwrap();
                current -= (k - last_x) * last_y;
                last_x = k;
                last_y = v;
                if v <= y {
                    break;
                }
                historical_min.remove(&k).unwrap();
            }
            current += (x - first_x) * first_y;
            current += (last_x - x) * y;
            historical_min.insert(x, y);
        }
        ans += current;
    }
    println!("{ans}");
}

posted:
last update: