题 意:给定一个树,求一个最少的节点集合,以至于每一条至少有一个节点在这个在这个集合中。
数据范围:
0 < n <= 1500
输入样例:
4
0:(1) 1 //0代表父节点 //括号里面的1 代表子节点的个数 之后就是子节点编号
1:(2) 2 3
2:(0)
3:(0)
5
3:(3) 1 4 2
1:(1) 0
2:(0)
0:(0)
4:(0)
输入样例:
1
2
思 路: 这里只给出树形dp的思路,对于每一个节点,都有两种决策,放或者不放。树形dp的核心思路是递归,从子节点一次把最优的状态往上传。那么为什么这题可以用树形dp呢。因为这题用树形dp确实可以求出最优解。
dp[v][1] 表示节点放入这个最小集合set,dp[v][0]表示不放入
那么父节点放入了,子节点可以放入,也可以不放入,取决于子节点的状态。如果父节点没有放入,那么子节点必须放入。状态转移方程:
dp[v][1] += min(dp[u][0],dp[u][1])
dp[v][0] += dp[u][1]
关键点 确定这是一个树形dp的题型。
关键点 判断怎么用树形dp的状态也是一个关键点。
关键点 状态转移方程。
收 获: 对树形dp有了更深刻的理解。
//每个点都可以选择放或者不放
//我们要求的是最少的,可以监视稍有的节点.
//关键点1:状态转移方程
//dp[i][1] 表示第i个节点放
//dp[i][0] 表示第i个节点不放
//dp[i][1] = min(dp[u][0],dp[u][1]);
//dp[i][0] += dp[u][1]
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 6e3+5;
int dp[maxn][2];
int parent[maxn];
int n;
void tree_dp(int root){
for(int i=1;i<=n;i++){
if(parent[i] == root){
tree_dp(i);
dp[root][1] += min(dp[i][1],dp[i][0]);
dp[root][0] += dp[i][1];
}
}
}
int main() {
while(~scanf("%d",&n)){
memset(parent,0,sizeof(parent));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
dp[i][1] = 1;
int f,cs;
scanf("%d:(%d)",&f,&cs);
f = f+1;
for(int i=0;i<cs;i++){
int temp;
scanf("%d",&temp);
temp = temp+1;
parent[temp] = f;
}
}
int root = 1;
while(parent[root]!=0){
root = parent[root];
}
tree_dp(root);
int MAX = min(dp[root][0],dp[root][1]);
printf("%d\n",MAX);
}
return 0;
}