定义
树形dp意为在树形结构上进行动态规划,由于在树上,所以使用 dfs 的方式。
需要注意的是,每个状态的转移都在回溯时完成。即先遍历到最深的节点,在依次向上转移。这符合动态规划的一般思路(将子问题推广到整体)
特点:选某个节点就会有相应的前提(选与不选其前提节点)
注:本文中讲解的树形dp分为两种:简单dp与树形背包
基础树形dp
步骤
1、链式前向星存图
2、找到根节点
3、dfs动态规划:本题型中定义 f[i][j]表示考虑节点i的子树,j=0或1表示选与不选节点i
4、输出 max(f[root][0] , f[root][1])
dp详细步骤
参考例题 没有上司的舞会
void dfs(int fa){
f[fa][1]=h[fa];//初始化,选该节点就会得到h[i]的快乐值
f[fa][0]=0;//不选则没有
for(int i=head[fa];i;i=edg[i].next){//遍历该节点子树
int v=edg[i].v;
dfs(v);//递归,回溯时进行转移
f[fa][0]+=max(f[v][1],f[v][0]);//不选该节点,其子节点仍有两种选择
f[fa][1]+=f[v][0];//选该节点就不能选其子节点
}
}
树形背包
算法解释
顾名思义就是在树形结构上做背包问题,同样用dfs遍历树,在每一次遍历时用背包问题的操作解决
算法解决
相同则不再赘述
1、定义dp数组 f[i][j][0或1]表示考虑节点i的子树,选取j个点,选与不选节点i
在部分题目中,i与其子节点存在依赖,即选i的子节点必须选i,就不需要第三维
例题讲解
题目分析
每一门课的直接选修课视为其父节点
链式前向星存树,dfs遍历,注意根据题目描述可能会出现森林,所以将每一棵树都连接在节点0上
状态转移实现
不妨设f[i][j][k]表示以i为根节点的子树,考虑前j个节点选k门课的方案数
初始化:因为1号节点是根节点,显然递推起点f[i][1][1]=val[i]
这样很容易得到状态转移方程
f[i][j][k]=max(f[i][j-1][k],f[son][所有节点数][l]+f[i][j-1][k-l]);
但是问题来了
这样开三维数组不会炸空间吗
也许本题不会
但是我们可以很显然的发现
空间是可以优化的
只要稍稍改变循环顺序即可
我要用到j-1的内容都是满足l<k的,所以倒着循环k
这样就可以使我们在一个数组中当前值和上面我们用到的值完全不影响
代码实现
#include<bits/stdc++.h>
using namespace std;
int n,w[114514],f[1141][802],x,y,root,z;
int idx,head[114514],q;
struct edge{
int v,next,w;
}edg[214541];
void conect(int x,int y){
idx++;
edg[idx].v=y;
edg[idx].next=head[x];
head[x]=idx;
}
void dfs(int u){
for(int i=head[u];i;i=edg[i].next){
int y=edg[i].v;
dfs(y);
for(int j=q+1;j>=1;j--){
for(int k=0;k<j;k++){
f[u][j]=max(f[y][k]+f[u][j-k],f[u][j]);//考虑u的子树,选j门课,从其子节点中去最大值
}
}
}
}
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>x>>f[i][1];//初始化
conect(x,i);//链式前向星
}
dfs(0);//用0连接
cout<<f[0][q+1];//从0开始,多个0所以q+1门课
return 0;
}
蒟蒻的第一篇blog,,,,,,:):):)