树上背包
(!!重点)树上背包涉及子树合并的时间复杂度证明:
一棵大小为 \(n\) 的树,进行如下循环:
void DFS(int u){
siz[u] = 1;
for(auto v : G[u]){
DFS(v);
for(int i = 1; i <= siz[u]; i ++){
for(int j = 1; j <= siz[v]; j ++){
f[u][i + j] <- f[u][i] * f[v][j]
...
}
}
siz[u] += siz[v];
}
}
这里可见外层循环中的子树 \(u\) 的大小 \(siz[u]\) 是在合并过程中动态增加的;而内层循环中的子树大小 \(v\) 固定。这样的循环看起来时间复杂度为 \(O(n^{3})\),实际上却是 \(O(n^{2})\)。
证明:对于 \(\forall\) 点对 \((x, y)\),\(lca\) 是唯一的;而在 \(siz[u]\) 与 \(siz[v]\) 的两个内层循环中,可以看作是在枚举所有 \(lca\) 为 \(u\) 的点对,并且每个点对最多只会被枚举 \(1\) 次。而每个点 \(u\) 都会被当作 \(lca\) 一次,因此总枚举次数实际上是树中所有点对的数量,为 \(O(n^{2})\)。
其他有关树上背包的例题补充:
P1272
本题中,将子树内节点个数当成体积,删边个数当成价值,则相当于求最小总价值,是一个典型的背包模型。
题解看置顶即可,讲解得很详细易懂。
P1273
本题虽然求的是选择的最大叶节点个数,那么很容易就将其作为背包模型中的价值。其实本题恰恰相反——将叶节点数作为背包体积,收益盈亏值作为价值。像这样 不将答案作为 \(dp\) 值,反而作为某个状态表示 的技巧也比较常见。
2025杭电多校3 1010 \(\space\)
注意本题卡空间,不能开全局的 \(dp\) 数组,因此本题需要在 \(dfs\) 函数内开数组,并作为函数的传参类型传递参数。只需要维护“原子树”与“新子树”之间的递推关系即可(对应代码的 \(dp, ndp\))。可以学习一下这种优雅写法以节省空间。
edu 106 F
不是单纯的树上背包,但是在优化树形 \(dp\) 时利用了树上背包的复杂度证明。

浙公网安备 33010602011771号