最优的情况是一条边正好站两个企鹅,这样会使得保留下来的边最少。
我们怎么求呢?
设ans为在这棵树中满足一条边被两个点站的点对的个数*2, 即点数。
那么如果ans >= k,直接输出(k+1)/2.
如果ans < k, 那么企鹅的站位一定有出现菊花图的样子,我们至多可以满足ans个点找到自己的匹配,剩下的(k-ans)个企鹅只能和别的企鹅链接形成菊花图的样子。
这样它自己站一条边, 所以输出ans / 2 + (k - ans)。
接下来的问题是如何找出ans。
考虑树形DP, 设f[i][0/1]为i的子树中,i这个节点选/不选的最大的两个点相互匹配的点数。
那么显然有f[u][0] += f[v][1];
f[u][1] = max(f[u][0] - f[v][1] + f[v][0] + 2),
解释一下:把式子变一下 $large f[u][1]=(sum f[v'][1])-f[v][1]+f[v][0]+2$
因为这个点和枚举的v形成了一个匹配, 所以+2.
写起来不是很难。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; inline int read() { int res=0;char c=getchar();bool f=0; while(!isdigit(c)) {if(c=='-')f=1;c=getchar();} while(isdigit(c))res=(res<<3)+(res<<1)+(c^48),c=getchar(); return f?-res:res; } int T, n, k; struct edge { int nxt, to; }ed[200005]; int head[100005], cnt; inline void add(int x, int y) { ed[++cnt] = (edge){head[x], y}; head[x] = cnt; } int f[100005][2]; void dfs(int x, int fa) { for (int i = head[x] ; i ; i = ed[i].nxt) { int to = ed[i].to; if (to == fa) continue; dfs(to, x); f[x][0] += f[to][1]; } for (int i = head[x] ; i ; i = ed[i].nxt) { int to = ed[i].to; if (to == fa) continue; f[x][1] = max(f[x][1], f[x][0] - f[to][1] + f[to][0] + 2); } } int main() { freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout); T = read(); while(T--) { cnt = 0; memset(head, 0, sizeof head); memset(f, 0, sizeof f); n = read(), k = read(); for (int i = 1 ; i <= n - 1 ; i ++) { int x = read(); add(x, i + 1), add(i + 1, x); } dfs(1, 0); int ans = max(f[1][1], f[1][0]); if (ans >= k) printf("%d ", (k + 1) / 2); else printf("%d ", ans / 2 + (k - ans)); } return 0; }