树形DP就是在树的基础上做动态规划
树形DP有两个方向:
1.叶->根,回溯是从叶子结点往上更新
2.根->叶,往往是在从叶往根dfs一遍之后(相当于预处理),再重新往下获取最后的答案。
题意
某大学有 n 个职员,编号为 1…n。
他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。
现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 R[i] ,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。
所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
思路
这道题是树形DP中的典中典,从叶子往根回溯。
建树:根据上司和员工的从属关系建立一个有向树
每个结点有一个价值,父节点和子节点不能同时选。
每个结点都是选和不选两种选择。
状态转移数组:
dp[u][0]为u结点不去,获得的子树的最大价值
dp[u][1]为u结点去,获得的子树d饿最大价值
可以看出一共有三种情况和对应的状态转移方程:
1.上司去,员工不去dp[u][1] = r[u] + dp[v][0]
2.上司不去,员工去dp[u][0] = dp[v][1]
3.上司不去,员工不去dp[u][0] = dp[v][0]
第二种和第三种情况合并dp[u][0] = max{dp[v][1],dp[u][0]}
对根节点进行dfs,最终答案为max{dp[root][0],dp[root][1]}
#include <iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAX_N = 6010; //最多结点数
int tot; //标记边的序号
int head[MAX_N],nxt[MAX_N],ver[MAX_N]; //建树要用到的数组
int r[MAX_N],flag[MAX_N]; //r为价值,flag标记u是否有上司
int dp[MAX_N][2];
void addedge(int u,int v){ //根据邻接表建树的过程
ver[++tot] = v; //tot条边指向的点为v
nxt[tot] = head[u]; //nxt保存以u为始点的下一条边的序号
head[u] = tot; //head[u]保存以u为始点的边的序号
}
void dfs(int u){
dp[u][0] = 0; //初始化u不去得到的dp
dp[u][1] = r[u]; //初始化u去得到的dp
for(int i = head[u]; i;i = nxt[i]){ //搜索u的所有孩子
int v = ver[i];
dfs(v);
dp[u][1] += dp[v][0];
dp[u][0] += max(dp[v][0],dp[v][1]);
}
}
int main(){
int n;
cin >> n;
for(int i = 1;i <= n;i++)
cin >> r[i];
int u,v;
for(int i = 1;i <= n-1;i++){
cin >> v >> u;
addedge(u,v);
flag[v] = 1; //v有上司
}
int root;
for(int i = 1;i <= n;i++){ //寻找根节点
if(!flag[i]){
root = i;
break;
}
}
dfs(root); //从根节点开始dfs
cout << max(dp[root][0],dp[root][1]) << endl; //根节点也有选和不选两种选择
return 0;
}