Codeforces Round #801 (Div. 2) - D2. Tree Queries (Hard Version)
树形DP
题意
有 \(n(1<=n<=2*10^5)\) 个结点的树,可任选 k 个观察点 \(x_1,x_2...x_k\),对于任意的某个点 t,可以分别给出到这 k 个观察点的距离(边权均为1)求 k 的最小值,使得任意两个结点的这 k 个距离不完全相同(即可以通过到这 k 个观察点的距离,区别出任意两个点)
思路
- 对于 D1 的 n <= 2000, 可枚举根,分别建树
- 如果根是一个观察点,那么对于任意一个子树 u,它的子树 \(v_i\) 中最多只能有一个子树中完全没有观察点(如果有两个子树内都没有观察点,那这两个子树将无法区分出来)
- 树形DP求出 \(f[u]\) :以 u 为根的子树内至少要有几个观察点,答案为 \(f[root]\) + 1,因为根一定是观察点
- 对于 \(n<=2*10^5\), 可换根dp,但比较复杂;画图一下可以看出(感觉比赛的时候打表找规律更好一点),如果以度为 >= 3 的为根,根这个观察点是不必要的,因此找一个度 >= 3 的dp即可,答案不用 + 1
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
const int INF = 1e9;
vector<vector<int> > G(N);
int n;
int f[N];
void add(int u, int v)
{
G[u].push_back(v);
}
void init()
{
for (int i = 1; i <= n; i++)
G[i].clear();
}
int dfs(int u, int fa)
{
int sum = 0, k = 0;
for (int v : G[u])
{
if (v == fa)
continue;
int t = dfs(v, u);
if (!t) k++;
sum += t;
}
return sum + max(k - 1, 0);
}
int solve()
{
if (n == 1)
return 0;
int root, maxn = 0;
for (int i = 1; i <= n; i++)
{
if (G[i].size() > maxn)
{
maxn = G[i].size();
root = i;
}
}
if (maxn <= 2)
return 1;
return dfs(root, -1);
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while(T--)
{
cin >> n;
init();
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
cout << solve() << endl;
}
return 0;
}