実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 816
問題概要
個の頂点からなる木が与えられ、 番目の辺は頂点 と を結んでいる。この木の部分木のうち、指定された 個の頂点 をすべて含むようなものの頂点数の最小値を求めよ。
制約
- 入力はすべて整数。
考察
タイトルにある通り、求めるべき木は「最小シュタイナー木」と呼ばれるものである。ただ、本来の最小シュタイナー木は辺に重みが付いたものであり、インターネットに転がっているアルゴリズムは 程度が限界らしく、本問の制約では間に合わない。
では、ここからは「残すべき頂点とはどのような頂点か?」を考えてみよう。
与えられた木 の中の任意の頂点 について、 を根とする部分木を とする。実は、 の中の任意の頂点 が のいずれかに該当する場合、頂点 は削除できない頂点である。
なぜなら、 間には辺がただ一つしか存在しないので、 を削除すると に到達できなくなってしまうからである。
逆に、そうでない頂点 は削除してよいことになる。
これをどう実装していくかだが、簡単なのは から DFS で頂点を走査していく方法だろう。
コードではcnt
という配列を用意して、cnt[i]
に頂点 を根とする部分木の頂点の中で に含まれているものの個数を格納している。
となる頂点 は残すべき頂点なので、最後にこの個数を出力すればよい。
コード
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) template <class T> using Graph = vector<vector<T>>; // ======================================== // void dfs(Graph<int> &G, set<int> &V, vector<int> &cnt, int now, int pre) { if (V.count(now)) cnt[now]++; for (auto &v : G[now]) { if (v == pre) continue; dfs(G, V, cnt, v, now); cnt[now] += cnt[v]; } } int main() { int N, K; cin >> N >> K; Graph<int> G(N); rep(i, 0, N - 1) { int A, B; cin >> A >> B; G[A - 1].push_back(B - 1); G[B - 1].push_back(A - 1); } set<int> V; rep(i, 0, K) { int v; cin >> v; V.insert(v - 1); } vector<int> cnt(N, 0); dfs(G, V, cnt, *V.begin(), -1); int ans = 0; for (auto &x : cnt) { if (x >= 1) ans++; } cout << ans << endl; }
実装時間 : 45分