【总结】树上背包

一般形式

P2014

\(f[x,t]\)\(x\) 的子树中选 \(t\) 们课的最大价值。

\[f[x,t]=\max_{\sum c_i=t-1} \{ f[i,c_i] \} + score[x] \]

对于 \(\max_{\sum c_i=t-1} \{ f[i,c_i] \}\) 相当于一个分组背包模型,对于每个儿子的决策相当于一个物品组。

直接做的复杂度为 \(O(nm^2)\)

void dfs(int x){
    vis[x] = true;
    for (int i = head[x]; i;i = fail[i]) {
        int v = edge[i];
        if (vis[v]) continue;
        dfs(v); 
        for (int t = m; t > 0; t--)
            for (int j = 1; j <= t; j++)
                f[x][t] = std::max(f[x][t],f[x][t - j] + f[v][j]);
    }
    if (x != 0)
        for (int i = m; i >= 1; i--)
            f[x][i] = f[x][i - 1] + a[x];
}

树上背包的上下界优化

证明链接

复杂度 \(O(nm)\)

void dfs(int x){
    vis[x] = true; size[x] = 1;
    for (int i = head[x]; i;i = fail[i]) {
        int v = edge[i];
        if (vis[v]) continue;
        dfs(v);
        for (int t = std::min(m,size[x] + size[v]); t > 0; t--)
            for (int j = std::max(1,t - size[x]); j <= t && j <= size[v]; j++)
                f[x][t] = std::max(f[x][t],f[x][t - j] + f[v][j]);
        size[x] += size[v]; //细节:在最后更新size保证 t - size[x] 的 bound
    }
    if (x != 0)
        for (int i = m; i >= 1; i--)
            f[x][i] = f[x][i - 1] + a[x];
}

技巧:体积价值互换后二分

全程摸鱼计划

直接按每个点的点权为价值做 \(\text{DP}\) 不好做。

体积和价值互换后,其依然能保证单调性,即随着体积的增大价值也一定增大

那么就可以将体积价值互换,每次查询二分。

具体地:考虑 \(\text{DP}\) 出选 \(m\) 个点的最小花费,随着花费随着点数的增加单调递增。

那么就可以设计一个树上背包 \(f[u,v,0/1/2]\) 表示以 \(u\) 为根的子树中选 \(v\) 个点不选根节点且都摸鱼,选根节点且都摸鱼,选根节点且除了根节点以外其他点都摸鱼的最小花费。

上下界优化后复杂度为 \(O(nm)\)

总体思想即:原来的价值变体积,原来的价值变花费,DP 之后二分最大体积。

代码记录

状态设计与代码实现的细节

  • 对于树形背包根节点的分类讨论,有两种处理手法,一种是先考虑不选的情况在子树内做分组背包之后加入根节点,另一种就是设计状态时加一维表示根节点是否选择,而对于加一维的方法需要主义不同状态间的转移顺序。
posted @ 2021-08-31 11:56  Themaxmaxmax  阅读(72)  评论(0编辑  收藏  举报