1. 程式人生 > 實用技巧 >[NOIP模擬賽] 拆網線

[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;
}