Birjik and Nicole's Tree Game | HourRank 20

復習用コメント付きコード

/*
思いつき方
頂点を黒く塗る
その黒い頂点を含む部分木は?
根まで行く距離が長過ぎる
HL分解する
*/

/*
HL分解
Heavy-Light Decomposition
木の構築 O(|V|+|E|)
あらかじめ与えられた木について、頂点u,v間のパスをセグメント木などで扱える半開区間の集合に変換する。区間の数はO(log(|V|))個。
ひとつの区間は連結した頂点たちにIDをふったもので、頂点の深さは全て異なる。その区間内では、(深さの値の差)=(IDの値の差) になっている.
1クエリ O(log(|V|))
スタックオーバーフロー回避版
*/
class HLDecomposition {
    int cur = 0;
    void bfs(const vector<vector<int> > &G) {
        queue<tuple<int, int, int> > que;
        vector<int> order;
        que.push(make_tuple(0, -1, 0));
        while (!que.empty()) {
            int u, pre, d;
            tie(u, pre, d) = que.front();
            que.pop();
            parent[u] = pre;
            depth[u] = d;
            order.push_back(u);
            for (int v : G[u])if (v != pre) {
                que.push(make_tuple(v, u, d + 1));
            }
        }
        reverse(order.begin(), order.end());
        for (int u : order) {
            sub[u] = 1;
            for (int v : G[u])if (v != parent[u])sub[u] += sub[v];
        }
    }

    void dfs_stk(const vector<vector<int> > & G) {
        stack<pair<int, int> > stk;
        stk.push(make_pair(0, 0));
        while (!stk.empty()) {
            int u, h, pre;
            tie(u, h) = stk.top();
            stk.pop();
            head[u] = h;
            id[u] = cur++;
            pre = parent[u];
            int heavy = -1, maxi = 0;
            for (int v : G[u]) {
                if (v != pre && maxi < sub[v]) {
                    maxi = sub[v];
                    heavy = v;
                }
            }
            for (int v : G[u]) {
                if (v != pre&&v != heavy) {
                    stk.push(make_pair(v, v));
                }
            }
            if (heavy != -1)stk.push(make_pair(heavy, h));
        }
    }
public:
    vector<int> parent, depth, sub, id, head;
    
    HLDecomposition(const vector<vector<int> > &G) {
        parent = depth = sub = id = head = vector<int>(G.size());
        bfs(G);
        dfs_stk(G);
    }

    /*
    パス(u, v)を半開区間の集合に変換する。
    O(log(|V|))
    */
    vector<pair<int, int> > goUpToLCA(int u, int v) {
        vector<pair<int, int> > res;
        while (true) {
            if (head[u] == head[v]) {
                if (depth[u] > depth[v])swap(u, v);
                res.emplace_back(id[u], id[v] + 1);
                break;
            }
            else {
                if (depth[head[u]] > depth[head[v]])swap(u, v);
                res.emplace_back(id[head[v]], id[v] + 1);
                v = parent[head[v]];
            }
        }
        return res;
    }
};

int N, Q, K;
vector<vi> G;
int vs[300001], ans[300001];

int main() {
    scanf("%d", &N);
    G.resize(N);
    rep(i, N - 1) {
        int u, v;
        scanf("%d%d", &u, &v);
        // 0-indexedとする。
        --u;
        --v;
        G[u].push_back(v);
        G[v].push_back(u);
    }

    HLDecomposition hld(G);
    // sgs[l]はlをheadとするセグメント[l, R)に対して
    // l<r<=R, r∈sgs[l]
    // を満たすような多重集合
    unordered_map<int, vi> sgs;

    scanf("%d", &Q);
    while (Q--) {
        sgs.clear();
        scanf("%d", &K);
        rep(i, K) {
            scanf("%d", &vs[i]);
            --vs[i];
            /*
            頂点vs[i]を含む部分木は
            vs[i], vs[i]の親、vs[i]の親の親、、、根
            つまりvs[i]の先祖すべてを根とする部分木すべて。
            これをすべて得るにはvs[i]から根までのパスを見るだけでいいが、
            O(|V|)かかる。
            かわりに、HL分解によってO(log|V|)個のパスを得る。
            */
            // log(|V|)個のパスに変換
            auto s = hld.goUpToLCA(0, vs[i]);
            each(p, s) {
                // ここが最も深い
                // O(log(N)ΣK)
                sgs[p.first].push_back(p.second);
            }
        }
        rep(i, K + 1)ans[i] = 0;

        each(p, sgs) {
            /*
            headのidをlとするようなパスについて考える。
            IDがlである頂点を根とする部分木は、黒い頂点をcn個含む。
            */
            int l = p.first, cn = sz(p.second);
            // rを昇順に見る。
            // より深い頂点へ向かっていく。
            sort(all(p.second));
            each(r, p.second) {
                /*
                r-1に対応する頂点が黒いので、
                rまで見たら黒色が1つ減る。
                [l, r)に対応するr-l個の頂点はcn個の黒い頂点を持つ。
                */
                ans[cn] += r - l;
                l = r;
                // 黒い頂点を1個減らす。(その黒い頂点よりも下へ行く)
                cn--;
            }
        }
        ans[0] = N;
        for (int i = 1; i <= K; ++i)ans[0] -= ans[i];
        rep(i, K + 1)printf("%d%c", ans[i], i != K ? ' ' : '\n');
    }
}