本题是树的最小点覆盖问题。
设想一个结点放士兵和不放士兵两种情况,如果放士兵,那它的孩子们放不放士兵都无所谓,如果不放士兵,那孩子们必须放士兵(否则将无法覆盖)

那么很容易想到状态转移方程:
dp[root][1]=1+图片说明 min(dp[son][1],dp[son][0])
dp[root][0]=图片说明 dp[son][1]

本题注意事项:
1.直接用一个字符变量c,吃掉所有多余的括号等无关内容
2.多组输入,记得清空邻接表和dp数组
3.最终的答案是在根结点的(因为你是从叶子一路往上推)
4.由于问题求解的是最小答案,跟以往树形dp经典问题不同,本题在状态转移的时候应当取min的操作

以下是代码:

#include <iostream>
#include<string.h>
#include<string>
#include<queue>
#include<algorithm>
#include <cmath>
#include <vector>
#include <sstream>
using namespace std;
const int N = 1550;
bool havepar[N];
vector <int> G[N];
string s;
int dp[N][2];
int Troot;
int n;
void init()
{
    for (int i = 0; i < n; i++)
    {
        G[i].clear();
    }
    memset(havepar, 0, sizeof(havepar));
    memset(dp, 0, sizeof(dp));
}
void dfs(int root)
{
    if (G[root].size() == 0)
    {
        dp[root][1] = 1;
        dp[root][0] = 0;
    }
    for (int i = 0; i < G[root].size(); i++)
    {
        int to = G[root][i];
        dfs(to);
        dp[root][1] += min(dp[to][1], dp[to][0]);
        dp[root][0] += dp[to][1];
    }
    dp[root][1] += 1;
}
int main()
{
    while (scanf("%d", &n) != EOF)
    {
        init();
        int u;
        int num;
        char c;
        for (int i = 0; i < n; i++) 
        {
            scanf("%d%c%c%d", &u, &c, &c, &num);
            scanf("%c", &c);
            while (num--)
            {
                int v;
                scanf("%c%d", &c, &v);
                havepar[v] = true;
                G[u].push_back(v);
            }
        }
        for (int i = 0; i < n; i++)
        {
            if (!havepar[i]) {
                dfs(i);
                Troot = i;
            }
        }
        printf("%d\n", min(dp[Troot][1], dp[Troot][0]));
    }
}