上限为k的树形背包复杂度证明

(zhao)(chao)博客:lnzwz 树形背包总结

普通树形背包:

树形背包一般是设 \(f[u][i]\) 表示当前子树内选择了 i 个点(或者连通块大小为 i )的贡献。

对于每个点 u,第一层枚举所有儿子 v,第二层枚举 v 中选了 j 个点,第三层枚举之前的儿子们(或者加上根)选择了 k 个点,将 j+k 进行合并。

因为要省去枚举儿子 v 的空间,所以要谨记滚动优化带来的枚举顺序问题,如果用 k 更新 j+k,则需要倒序枚举。

复杂度为 n^2,简单的证明和例题:[HAOI2015]树上染色

void TREE(int u,int fa) {
    siz[u] = 1;
    //dp[u][]  初始化
    for(int v : e[u]) {
        if(v==fa) continue;
        TREE(v,u);
        for(int j=0; j<=siz[v]; j++)
            for(int k=siz[u]; ~k; k--)
                dp[u][j+k] = dp[u][k] * dp[v][j];
        siz[u] += siz[v];
    }
}

上限为k的背包:

2023 ICPC 济南站 B题,求将树切成大小为 k 或 k+1 的块的方案。

这种题我们 \(f[u][i]\) 的第二维只需要维护到 k+1。此时的时间以及空间复杂度均为 O(nk)。

代码中只要把枚举上界改为 \(min(siz,k)\) 即可(两棵子树和的上限也要改为k)。

证明有如下两种方式:


一、分类讨论:

复杂度瓶颈在于枚举父亲 u 的连通块大小,枚举儿子 v 的连通块大小进行合并。将所有的合并行为分成两种情况:

1:两个枚举上界均为子树大小 siz:说明我们要合并的子树大小都小于 k,我们把所有 siz 小于 k 的树单独拿出来,会发现第一种情况相当于只在这些树中进行树形背包,假设每棵树大小为 \(s_i\) ,根据普通的树形背包复杂度,这些小树的复杂度之和为 \(s_1^2+s_2^2+...+s_m^2\) ,根据基本不等式可得复杂度上限为 \(O(\frac nk*k^2) = O(nk)\)。即大小均为 k,有 n/k 个。

2:两个枚举上界至少有一个为 k:发现这种情况子树与它的父亲合并之后总和大于 k,因此每个子树可以看做只被合并一次(之后只需枚举到k),设这些子树大小为 \(s_i\),则复杂度上限为 \(k*s_1+k*s_2+...+k*s_m\le kn\)。例子为菊花图。


二、数形结合:

更优美些的证明:将树用dfs序拍扁成序列,父亲与儿子的关系就形如线段树的结构。

在父亲合并儿子时,可以视作合并两个线段,左边线段取后 x 的长度,右边线段取前 y 的长度,满足 x+y<k。由于任意一次合并的线段位置都是唯一的,所有的合并复杂度相当于 n 中长度不超过 k 的子串个数。复杂度为 O(nk)。

posted @ 2024-02-03 22:01  maple276  阅读(86)  评论(0编辑  收藏  举报