dp进阶,树形背包(dfs+01)
顾名思义,就是在对树进行搜索的时候,由于限制了子节点选根节点必选和节点数限制,所以需要额外利用背包来维护最大值
假设根节点就是0,我们很容易 发现,这就是一个正常的树求和,但是限制了节点数量,所以需要用背包去规划这个限制(容量)
局部分析:取一个倒数2小的子节点,可以求出该节点下面选择0-n个子节点的最大值
dp[x][i]:x代表该节点的序号,i代表这个节点占多大容量,dp[x][i]自然就是维护的最大值;将其上传,一直到【0】【m】的最大值
#include <bits/stdc++.h>
using namespace std;
int n,m;
struct ed{
int next,to;
}e[1000];//ed
结构体用于存储边信息,采用邻接表存储树结构。当然也可以用vector<vector<int>>
to
:表示当前边的终点节点编号。在树的结构中,它指的是子节点的编号。next
:指向下一条从同一节点出发的边的索引。在邻接表中,它用于将同一个起点的所有边连接成一个链表。
int rt,h[1000],o,v[1000],dp[1000][1000];//h[1000]
是邻接表的头数组,o
是边计数器。v[1000]
存储每个节点的权值。dp[u][t]
表示以节点u
为根的子树中选择t
个节点能获得的最大权值。
void add(int x,int y){//邻接表添加边:将节点y
添加为节点x
的子节点。(用vector<vector<int>>就 // 不用写这个惹。但是处理的效率会下降)
e[++o].next=h[x];
h[x]=o;
e[o].to=y;
}
邻接表的工作原理
- 每个节点通过一个头指针(
h
数组)指向第一条边 - 每条边(
e
数组中的元素)包含两个信息:to
:表示这条边指向的节点next
:指向下一条边的索引,形成链表
void dfs(int u,int t){
if (t<=0) return ;
for (int i=h[u]; i; i=e[i].next){//遍历
int p = e[i].to;//到子节点i
for (int k=0; k<t; ++k)
dp[p][k] = dp[u][k]+v[p];//在父节点 u
已经选择了 k
个节点的基础上,选择子节点 p
(并获得其权值 v[p]
)
dfs(p,t-1);
for (int k=1; k<=t; ++k)
dp[u][k] = max(dp[u][k],dp[p][k-1]);
}
}
动态规划核心函数:
- 终止条件:如果剩余选择次数
t
小于等于 0,直接返回。 - 初始化子节点 DP 值:对于每个子节点
p
,初始化dp[p][k]
为dp[u][k] + v[p]
,表示在父节点已选状态下选择子节点。 - 递归处理子树:递归调用
dfs(p, t-1)
处理子树,限制子树最多选择t-1
个节点。 - 更新父节点 DP 值:通过子节点的 DP 值更新父节点的 DP 值,取最大值。
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int a;
scanf("%d%d",&a,&v[i]);
if(a)
add(a,i);
if(!a)add(0,i);
}
dfs(0,m);
printf("%d",dp[0][m]);
}