[NOIP模拟赛] 拆网线
企鹅国的网吧们之间由网线互相连接,形成一棵树的结构。现在由于冬天到了,供暖部门缺少燃料,于是他们决定去拆一些网线来做燃料。但是现在有 \(K\) 只企鹅要上网和别人联机游戏,所以他们需要把这 \(K\) 只企鹅安排到不同的机房(两只企鹅在同一个机房会吵架),然后拆掉一些网线,但是需要保证每只企鹅至少还能通过留下来的网线和至少另一只企鹅联机游戏。
所以他们想知道,最少需要保留多少根网线?
// 保留图上 k 个点,总边数最小,且最少两个点之间联通(保留的点没有孤立点)
// 保证输入数据是一颗树
// 其实就是树上二分图匹配
# include <iostream>
# include <cstdio>
# include <cstring>
# define MAXN 100005
struct edge{
int v, next;
}e[MAXN<<1];
int hd[MAXN], cntE;
int f[MAXN][2]; // 是否包括当前节点的子树中的最大两两匹配数
// 记匹配数为 sum
// sum * 2 >= k,ans = (k+1)/2
// sum * 2 < k,ans = sum + (k - sum*2)
void AddE(int u, int v);
void DFS(int now, int fa);
int main(){
int T;
scanf("%d", &T);
int n, k, sum;
while(T--){
memset(hd, 0, sizeof(hd));
memset(f, 0, sizeof(f));
cntE = 0;
scanf("%d%d", &n, &k);
for(int i = 2, con; i <= n; i++){
scanf("%d", &con);
AddE(i, con); AddE(con, i);
}
DFS(1, 0);
sum = std::max(f[1][0], f[1][1]);
if(sum * 2 >= k){
printf("%d\n", (k+1)/2);
}
else{
printf("%d\n", k - sum);
}
}
return 0;
}
void DFS(int now, int fa){
for(int i = hd[now]; i; i = e[i].next){
if(e[i].v == fa){
continue;
}
DFS(e[i].v, now);
f[now][0] += f[e[i].v][1];
}
for(int i = hd[now]; i; i = e[i].next){
if(e[i].v == fa){
continue;
}
f[now][1] = std::max(f[now][1], f[now][0] - f[e[i].v][1] + f[e[i].v][0] + 1);
}
}
void AddE(int u, int v){
e[++cntE] = (edge){v, hd[u]};
hd[u] = cntE;
}