版权声明:转载无所谓的。。。 https://blog.csdn.net/xuxiayang/article/details/82713782
大意
给定一张 个点, 条边的无向联通图,现要在图中至少有一个由 个点组成的联通分量中的点数必须不小于2的情况下,割去尽量多的边。
思路
树形
一条边可以用两只企鹅站,这样的一条点对,越多越好。
如果是
对点,
,那么只需要
条边。
否则,需要
条边。
现在问题就转为求这样的点对有多少。
设 表示以 为根的子树中能够两两配对的最大点数,不包含节点
设 表示以 为根的子树中能够两两配对的最大点数,包含节点
得到方程
代码
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;int n,m,t,f[100001],g[100001],ans;
vector<int>son[100001];
bool vis[100001];
inline int read()//输入优化
{
int f=0,d=1;char c;
while(c=getchar(),c<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),c>47&&c<58)f=(f<<3)+(f<<1)+c-48;
return d*f;
}
inline void write(register int x){if(x>9)write(x/10);putchar(x%10+48);return;}//输出优化
inline void dfs(register int x,register int fa)//树形dp
{
if(vis[x]) return;
vis[x]=true;//记得标记
f[x]=0;g[x]=0;//初始化
int sum=0;
for(register int i=0;i<son[x].size();i++)
{
int y=son[x][i];
if(y==fa) continue;
dfs(y,x);
g[x]+=max(f[y],g[y]);//动态转移
sum+=f[y];//计算cgm f[y]
}
for(register int i=0;i<son[x].size();i++)
{
int y=son[x][i];
if(y==fa) continue;
f[x]=max(f[x],sum-f[y]+g[y]+1);//动态转移
}
return;
}
signed main()
{
t=read();
while(t--)
{
n=read();m=read();
for(register int i=0;i<=n;i++)son[i].clear();
memset(vis,0,sizeof(vis));//初始化
for(register int i=1,u;i<n;i++)
son[u=read()].push_back(i+1),son[i+1].push_back(u);//建边
dfs(1,-1);
ans=max(f[1],g[1]);//得出点对
if((ans<<1)>=m) write((m+1)>>1),putchar(10);
else write(ans+m-(ans<<1)),putchar(10);//输出
}
}