该题是点的最小覆盖集问题。
题意:求覆盖一个树所有的点,需要覆盖最小的次数,覆盖一个点需要耗费一次次数,覆盖完以后,那么相邻的点也被覆盖。

对于无向树,直接随便找一个结点作为根,然后把整棵树支起来,就有了根和叶子!
需要一点想象力,反正无向树怎么旋转都可以。
那么寻找当前点的状态,如果一个树都被覆盖,那么这一个点,肯定要么是自己覆盖的,要么是父亲覆盖的,要么是儿子覆盖的,就找到了三种状态。
状态1:自己覆盖
那么对于自己的儿子而言,可以覆盖也可以不覆盖(求min就好了)
状态2:儿子覆盖
那么只要有至少有一个儿子是自己覆盖了的就好了
状态3:父亲覆盖
那么对于自己的儿子,可以是他本身覆盖,也可以是儿子的儿子覆盖。

对于特殊的点,要特殊考虑,有以下两种情况:
1.当前点没有父亲(根结点),那么该点的状态3不存在
2.当前点没有孩子(叶子结点),那么该点状态2不存在

状态转移方程:
dp[root][0]=1+min(dp[son][0],dp[son][1],dp[son][2])
自己覆盖的时候,孩子们的状态无所谓
如果root点有孩子:
dp[root][1]=min(dp[son][0],dp[son][1])+inc(if(dp[son][0]>dp[son][1])) inc=min(inc,(dp[son][0]>dp[son][1])))
如果root点没有孩子:
dp[root][1]=INF
如果root点有父亲:
dp[root][2]=图片说明 min(dp[to][0],dp[to][1])
如果root点没有父亲:
dp[root][2]=INF

有了状态转移方程,只要通过dfs搜索的时候把状态转移方程带入即可求解。
以下是代码:

#include <bits/stdc++.h>
using namespace std;
const int N=10100;
const int INF=0x3f3f3f3f;
vector <int> G[N];
int dp[N][3];//0表示该点覆盖 1表示该点儿子覆盖 2表示该点父亲覆盖
void dfs(int root,int pre)
{
    bool haveson=false;
    bool coverson=false;
    int inc=0x3f3f3f3f;
    for(int i=0;i<G[root].size();i++)
    {
        int to=G[root][i];
        if(to==pre) continue;
        haveson=true;
        dfs(to,root);
        dp[root][0]+=min(dp[to][0],min(dp[to][1],dp[to][2]));
        if(root==1) dp[root][2]=INF;
        else dp[root][2]+=min(dp[to][0],dp[to][1]);
        inc=min(inc,dp[to][0]-dp[to][1]);
        if(dp[to][0]<=dp[to][1])
        {
            coverson=true;
            dp[root][1]+=dp[to][0];
        }
        else
        {
            dp[root][1]+=dp[to][1];
        }
    }
    if(!haveson) dp[root][1]=INF;
    else if(!coverson) dp[root][1]+=inc;
    dp[root][0]+=1;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,0);
    printf("%d\n",min(dp[1][0],min(dp[1][1],dp[1][2])));
}