题目大意
题目
给定一棵由
N
N
N 个节点组成的无根树,删除其中的一些点和边,使剩下的点和边仍然能够组成一棵树,且包含给定的
K
K
K 个特殊点,问最少剩下几个点。
思路
我们可以发现,这棵无根树的根必须是给定的特殊点之一,不然根节点就可以删除,答案就不是最优。所以我们使用深度优先搜索遍历这棵树,根节点为给定的点中的任意一个,不妨使用第一个读入的特殊点。深搜函数中,我们要考虑一下这个点取还是不取。如果这个点的后代中有特殊点,那么这个点是一定要取的,因为树是一个无向无环连通图,要是不取的话我们就没有办法取到那个特殊点了。
伪代码/代码框架
我们用
f
i
f_i
fi 表示
i
i
i 号节点要不要取,
f
i
=
1
f_i=1
fi=1 表示要取,
f
i
=
0
f_i=0
fi=0 则表示不取。
d
f
s
dfs
dfs 函数:
- 有两个参数,分别是当前点 x x x 和父节点 f a fa fa。
- 如果 x x x 是特殊点,那么一定要取。
- 枚举 x x x 的邻居 y y y,如果不是父亲(即是儿子),那么递归,考虑儿子要不要取, f x = f x ∨ f y f_x = f_x \lor f_y fx=fx∨fy。
主函数:
- 读入,跑一遍深搜,统计必须取的点的个数。
代码
#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;
}