加载中...

树上背包

(!!重点)树上背包涉及子树合并的时间复杂度证明:

一棵大小为 \(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

本题中,将子树内节点个数当成体积,删边个数当成价值,则相当于求最小总价值,是一个典型的背包模型。

题解看置顶即可,讲解得很详细易懂。

三维code
二维code

P1273

本题虽然求的是选择的最大叶节点个数,那么很容易就将其作为背包模型中的价值。其实本题恰恰相反——将叶节点数作为背包体积,收益盈亏值作为价值。像这样 不将答案作为 \(dp\) 值,反而作为某个状态表示 的技巧也比较常见。

code

2025杭电多校3 1010 \(\space\)

注意本题卡空间,不能开全局的 \(dp\) 数组,因此本题需要在 \(dfs\) 函数内开数组,并作为函数的传参类型传递参数。只需要维护“原子树”与“新子树”之间的递推关系即可(对应代码的 \(dp, ndp\))。可以学习一下这种优雅写法以节省空间。

code

edu 106 F

不是单纯的树上背包,但是在优化树形 \(dp\) 时利用了树上背包的复杂度证明。

code

posted @ 2025-07-10 09:54  jxs123  阅读(84)  评论(0)    收藏  举报