D - Minimum Steiner Tree Editorial by MMNMM


木 DP を用いることでもこの問題を解くことができます。

指定された頂点のひとつを根として選び、根付き木とします。 このとき、答えとなる部分グラフで頂点 \(v\) を使うことの必要十分条件は、\(v\) を親とする部分木の中に指定された頂点が存在することです。

\(v\) に対するこの条件が成り立つかどうかは、\(v\) の子に対する答えをすべて求められれば確かめられます。

あとは、DFS などと共に木 DP を行うことでこの問題を解くことができます。

実装例は以下のようになります。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    using namespace std;
    unsigned N, K;
    cin >> N >> K;
    vector<vector<unsigned>> edges(N);
    for(unsigned i{1}; i < N; ++i){
        unsigned A, B;
        cin >> A >> B;
        edges[--A].emplace_back(--B);
        edges[B].emplace_back(A);
    }

    // use[i] := 答えとなる木で頂点 i を使う
    vector<bool> use(N);
    // 木 DP をするための根
    unsigned root;
    cin >> root;
    --root;
    use[root] = true;
    for(unsigned i{1}; i < K; ++i){
        unsigned V;
        cin >> V;
        use[--V] = true;
    }

    // 木 DP を行う
    // dfs(now, prev) := prev を親とした now 以下の部分木に使う頂点があるか
    [dfs{[&edges, &use](const auto& f, unsigned now, unsigned prev) -> bool {
        for(const auto next : edges[now])
            if (next != prev)
                use[now] = use[now] | f(f, next, now);
        return use[now];
    }}](unsigned v){
        dfs(dfs, v, v);
    }(root); // 使う頂点を根として木 DP

    cout << ranges::count(use, true) << endl;
    return 0;
}

posted:
last update: