題目大意
題目
給定一棵由 $N$ 個節點組成的無根樹,刪除其中的一些點和邊,使剩下的點和邊仍然能夠組成一棵樹,且包含給定的 $K$ 個特殊點,問最少剩下幾個點。
思路
我們可以發現,這棵無根樹的根必須是給定的特殊點之一,不然根節點就可以刪除,答案就不是最優。所以我們使用深度優先搜尋遍歷這棵樹,根節點為給定的點中的任意一個,不妨使用第一個讀入的特殊點。深搜函式中,我們要考慮一下這個點取還是不取。如果這個點的後代中有特殊點,那麼這個點是一定要取的,因為樹是一個無向無環連通圖,要是不取的話我們就沒有辦法取到那個特殊點了。
虛擬碼/程式碼框架
我們用 $f_i$ 表示 $i$ 號節點要不要取,$f_i=1$ 表示要取,$f_i=0$ 則表示不取。
$dfs$ 函式:
- 有兩個引數,分別是當前點 $x$ 和父節點 $fa$。
- 如果 $x$ 是特殊點,那麼一定要取。
- 列舉 $x$ 的鄰居 $y$,如果不是父親(即是兒子),那麼遞迴,考慮兒子要不要取,$f_x = f_x \lor f_y$。
主函式:
- 讀入,跑一遍深搜,統計必須取的點的個數。
程式碼
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n, k, cnt; // n,k含義見題面,cnt表示答案
int f[200010]; // 要不要取
vector<int> g[200010]; // 儲存
void dfs(int x, int fa) // x是當前節點,fa是其父節點
{
for (int i = 0; i < g[x].size(); i++) // 列舉鄰居
{
int y = g[x][i];
if (y == fa) continue; // 如果是父親,跳過
dfs(y, x);
f[x] |= f[y]; // 轉移
}
}
int main()
{
cin >> n >> k;
for (int i = 1; i < n; i++)
{
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a); // 建雙向邊
}
int root = 0;
for (int i = 1; i <= k; i++)
{
int v;
cin >> v;
f[v] = 1; // 先把特殊點標為1
if (!root) root = v; // 根節點
}
dfs(root, 0); // 呼叫
for (int i = 1; i <= n; i++)
cnt += f[i]; // 統計
cout << cnt << endl;
return 0;
}