[OI] 树上背包问题

树上背包问题的使用场景

当一个问题满足树的结构,而且需要让我们选取某些边或某些点,使权值取到最值。这样的问题是树上背包问题.

树上背包模板

树上背包问题使用dfs.
一般来说,树上背包问题的基本框架如下图:

void dfs(int s){
	for(i:遍历s的全部子节点){
		dfs(i);
		for(j:背包容量倒序遍历(01背包问题时)){
			for(k:0~j-1){
				f[s][j]=max/min(f[s][j],f[i.to][k]+f[s][j-k-v[root]]+i.w);
			}
		}
	}
}

可能有些难懂,下面来做些解释:

  • 定义 \(f[i][j]\) 为以 \(i\) 为根节点,背包容量为 \(j\) 时的最大权值.
  • 树上背包问题,依照背包问题的遍历方法,通常采用三层遍历,其中:第一层遍历全部子节点,第二层遍历背包容量.
  • 树上背包问题中,一棵树的全部容量要分为根结点占用的容量各个子树占用的容量,因此,第三层遍历当前子树占用当前树的容量.
  • 注意第三层循环中 \(k\) 最小是 \(0\) ,即一个都不分配,而最大值只能到 \(j-v[root]\) ,因为还要留一个分配给根节点.
  • 已知第三层遍历占用了 \(k\) 容量,权值在之前的dfs中已求出,应为 \(f[i.to][k]\) (这里 \(i.to\) 指子节点的根节点). 而根节点占用的容量为 \(v[root]\), 权值为 \(i.w\). 所以,剩下没被占用的总容量为 \(j-k-v[root]\) ,其权值我们其实可以这样表示: \(f[s][j-k-v[root]]\) (因为该值事先已求出),因此,得到状态转移方程为:

\[f[s][j]=f[i.to][k]+f[s][j-k-v[root]]+i.w \]

  • 树上背包问题的边界条件是非常需要注意的,有时限制边界条件可以节省时间,或者避免越界.
  • 建树时需要注意,该树需要是从父亲指向儿子的,否则无法快速找到子树.
  • 在无法判断父子关系时,树也可以建成一个相同形状的无向图. 在遍历时加入一个记录该次遍历父节点的参数 \(last\) 并添加 if(i.to==last) continue; 一句即可.

例题代码: 选课

posted @ 2024-02-16 20:47  HaneDaniko  阅读(35)  评论(0编辑  收藏  举报