题目:给你一棵树,用最少的链去覆盖这棵树,求链的最小总长度。
解析:num为叶子节点数,显然链数是(num+1)/2。如果是偶数,就是叶子节点到叶子节点,如果是奇数,那么就是在奇数-1情况下的树下加一条叶子到其祖先的链。
偶数的情况:从一个非叶子节点出发,如果其子节点的叶子节点是偶数,则ans+=2,如果是奇数,ans+=1。
奇数的情况:枚举一下那条单链所在的子树。
设dp[u][i][j] 在u的子树中,u的父边需要经过i次,j表示单链是否在该子树中。j=1就额外枚举单链所在子树即可,如果单链在该子树中,那么该树中的有效叶子节点-1(即奇数变偶数,偶数变奇数)。
树形dp的难点就在于子树和父节点的关系
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn=1e5+50;
int t;
int n;
int dp[maxn][2];
int eadg[maxn],son[maxn];
vector<int>v[maxn];
void dfs(int f,int x)
{
dp[x][0]=0;
dp[x][1]=inf;
int chd=0;
for(int i=0;i<v[x].size();i++)//先考虑x节点子节点各自走的步数
{
if(v[x][i]==f)
continue;
dfs(x,v[x][i]);
chd++;
son[x]+=son[v[x][i]];
int d=(son[v[x][i]]&1?1:2);//考虑x节点走到上一个节点步数,奇数+1,偶数+2
dp[x][0]+=dp[v[x][i]][0]+d;
}
for(int i=0;i<v[x].size();i++)//考虑x节点的叶子节点两两搭配之后的步数
{
if(v[x][i]==f)
continue;
if(son[v[x][i]]==1)
dp[x][1]=min(dp[x][0],dp[x][1]);
int d=(son[v[x][i]]&1?1:-1);
dp[x][1]=min(dp[x][1],dp[x][0]-dp[v[x][i]][0]+dp[v[x][i]][1]+d);
}
if(!chd) son[x]=1;
}
int main()
{
cin>>t;
while(t--)
{
memset(son,0,sizeof(son));
memset(eadg,0,sizeof(eadg));
scanf("%d",&n);
for(int i=0;i<=n;i++)
v[i].clear();
int a,b;
for(int i=0;i<n-1;i++)
{
scanf("%d%d",&a,&b);
v[a].push_back(b);
v[b].push_back(a);
eadg[a]++;
eadg[b]++;
}
if(n==2)
{
puts("1");
continue;
}
int root;
int cnt=0;
for(int i=1;i<=n;i++)
if(eadg[i]!=1)
root=i;
else
cnt++;
dfs(0,root);
printf("%d\n",dp[root][cnt&1]);
}
return 0;
}