D - Card Pile Query Editorial by Solalyth


いわゆる「操作を逆順に見る」方針でも解くことができます。

カード \(c\) をカード \(p\) に載せる操作 \((c, p)\) に対し、後に \(C_i = c\) である操作を行う場合、操作 \((c, p)\) を無視しても答えは変わりません。よって、各カード \(x\) について、\(C_i = x\) なる操作は最後の 1 回だけ行うとしてよいです。

考慮すべき操作 \((C_i, P_i)\) について有向辺 \(C_i \to P_i\) を張ったグラフを考えると、問題の制約からこのグラフは有向パスグラフの集まりとなります。頂点 \(x\) から辺を通れるだけ通って頂点 \(y\) にたどり着くとき、カード \(x\) は山 \(y\) に最終的に積まれることが分かります。

以上より、このグラフを適切に探索することで \(O(N+Q)\) で解くことができます。

各連結成分の頂点数が分かればよいので、UnionFind を用いて簡潔に実装することができます。時間計算量は \(O((N + Q) \cdot \alpha(N))\) です。

実装例 (Rust): https://atcoder.jp/contests/abc455/submissions/75276680

use {proconio::{input, marker::Usize1}, ac_library::Dsu};

fn main() {
    input! { N: usize, Q: usize, CP: [(Usize1, Usize1); Q] }
    
    let mut dsu = Dsu::new(N);
    let mut seen = vec![false; N];
    
    // 操作を逆順に見る
    // それまでに C_i = c として操作していなければ辺を張る
    for (C, P) in CP.into_iter().rev() {
        if !seen[C] { seen[C] = true; dsu.merge(C, P); }
    }
    
    for i in 0..N {
        print!("{} ", if seen[i] {0} else {dsu.size(i)});
    }
}

posted:
last update: