CS Academy Uniform Trees

リンク : https://csacademy.com/contest/archive/task/uniform-trees/statement/

問題概要

N頂点の木が与えられる、それそれの頂点にラベルがつけられる。以下の条件を満たす頂点集合の通り数を求めよ。
・頂点集合のうちある1つの頂点はほかのすべての頂点の祖先となる。これを根とする。
・根以外の頂点は頂点集合中の最も近い祖先とつなぐ。

解説

dp[i][j]を頂点iの子孫の頂点集合に関して、つながれた祖先からの頂点がjのラベルをつけられた働きをするときの通り数とすると、dp[i][j]=∏{(​x∈child(i))dp[x][j]+1}-1となる。このdpはi<=2×10^{5}、j<=2×10^{5}、となるため、静的に確保してしまうと、MLEする。そこでjに関しては中身が0のことが多いため、mapでもっておくことを考える。すると、子孫からのdpをうけとるときにマージテクなるものをつかうことができる。これは大きいほうに小さいものを入れることを繰り返せば{N}^{2}からNlogNに落とせるといったテクニックである。
メモ:マージテクは計算量空間量ともに削減する。

LL n, ans, a[210000],dp[210000];
map<LL, LL>mp[210000];
vector<vector<LL>>g;
void merge(LL a, LL b) {
    if (mp[a].size() < mp[b].size()) {
        swap(mp[a], mp[b]);
        swap(dp[a], dp[b]);
    }
    for (auto it = mp[b].begin(); it != mp[b].end(); it++) {
        if (mp[a].count(it->first)) {
            (dp[a] += (mp[a][it->first] *( it->second - 1 + MOD)) % MOD) %= MOD;
            mp[a][it->first] = (mp[a][it->first] * it->second) % MOD;
        }
        else {
            (dp[a] += it->second)%=MOD;
            mp[a][it->first] = it->second;
        }
    }
}
void dfs(LL cur, LL par) {
    rep(i, g[cur].size()) {
        LL v = g[cur][i];
        if (v == par)continue;
        dfs(v, cur);
        merge(cur, v);
    }
    LL num = (1 + dp[cur] + MOD - mp[cur].size()) % MOD;
    (ans += num) %= MOD;
    if (mp[cur].count(a[cur])) {
        (dp[cur] += num) %= MOD;
        (mp[cur][a[cur]] += num) %= MOD;
    }
    else {
        (dp[cur] += num+1) %= MOD;
        (mp[cur][a[cur]] += num+1) %= MOD;
    }
}
int main() {
    cin >> n;
    g.resize(n);
    rep(i, n) {
        LL x, y;
        cin >> x >> a[i];
        x--;
        if (i) {
            g[x].push_back(i);
            g[i].push_back(x);
        }
    }
    dfs(0, -1);
    cout << ans << endl;
    return 0;
}