[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;
一句即可.
例题代码: 选课