• 树形依赖背包问题的两种优化


    问题简述

    ​ 给定一颗 (n) 个点,以 (1) 为根的树,树上的每个节点上有 (1) 种物品。对于第 (i) 个节点上的第 (i) 种物品,可以选 任意个,每个的费用为 (c[i]) 元,价值为 (v[i]) 。且若选择了第 (i) 种物品,那么 (i) 的父节点的物品至少得选择一个。给定 (m) 元,求可能的最大价值。

    普通解法

    ​ 根据以往做树形DP的经验,我们可以设f[i][j]表示以 (i) 号节点为根的子树中用 (j) 元的最大价值。那么就有以下状态转移的方法:

    for(int j=0;j<=m;++j)
    for(int k=j;k>=0;--k)
        f[p][j]=max(f[p][j],f[son[p][i]][k]+f[p][j-k])
    

    ​ 这样的做法是 (mathcal{O}(nm^2)) 的。

    泛化物品解法

    ​ 考虑这样一种物品:它没有固定的单位价值,它的单位价值随费用的变化而变化。我们称这样的物品是泛化物品。泛化物品可以用函数G[]来表示,当用 (x(xin[0,m])) 元来购买这种物品时,它的价值为G[x]

    ​ 可以发现,普通解法中的f[i][]就是一个泛化物品。而普通解法的实质就是不断地合并多个泛化物品得到一个新的泛化物品,直至合并成最终的f[1][]。显然,合并两个泛化物品的复杂度为 (mathcal{O}(m^2)) ,这也是普通解法的效率瓶颈所在。普通的合并方法的效率已经无法提升了,我们不妨换新思路来合并。

    ​ 首先,合并一个泛化物品和一个普通物品的复杂度是 (mathcal{O}(m)) 的:

    for(int i=m;i>=c[x];--i)
        f[p][i]=f[p][i-c[x]]+v[x];
    

    ​ 并且,对于两个在定义域上所有取值都有交集的两个泛化物品,由于任意时候都不能同时都取,故合并的复杂度也是 (mathcal{O}(m)) 的:

    for(int i=1;i<=m;++i)
        f[p][i]=max(f[p][i],f[x][i]);
    

    ​ 于是,我们可以设计一种这样的算法:假设当前处理到以 (p) 为根的子树,枚举到子节点 (x) 。先强制选择一个 (x) ,合并一个泛化物品和一个普通物品,以此初始化得到f[x][]。当递归处理完以 (x) 为根节点的子树后,由于f[x][]的初始化是在f[p][]的基础上进行的,所以这是合并两个有交集的泛化物品,并以此更新f[p][]。也可以理解成f[x][]是强制选择至少一个 (x) 的泛化物品,而更新前的f[p][]是一个 (x) 都不选的泛化物品。对于 (p) 来说,在某一费用上,它只能选择取至少一个 (x) 或者不取,所以是两者取 (max)。代码如下:

    for(int i=head[p];i;i=nt[i]){//这份代码与上述写法略有不同,它将强制选择体现在了背包容量 -1 上。
        int y=to[i];
        for (int k=0; k!=C;++k) 
        	F[y][k]=F[p][k]+v[y];
        DP(v,C-1);
        for (int k=1; k<=C;++k) 
        	f[p][k] = max(f[p][k], f[y][k-1]);
    }
    

    ​ 至此,我们得到了一种 (mathcal{O}(n imes m)) 的做法。

    DFS序解法

    ​ 设f[i][j]表示DFS序大于等于i的点使用j元的最大价值(并且若自己被买自己的父节点必须被买);ind[x]表示DFS序为x的节点;oud[x]表示x节点的子树中的最大的DFS序。假设我们现在DP的是DFS序为i的节点。此时有两种完全互斥的情况: (i) 被买/ (i) 不被买。先强制 (i) 被买——由DFS序为 (i+1) 的点直接继承过来,再与自己做一次背包DP。此时的这个背包DP就是多重背包。

    int cur=ind[i];
    for(int j=m;j>=c[cur];--j)
    	f[i][j]=f[i+1][j-c[cur]]+w[cur];//强制选择 直接继承
    for(int j=1;j<=m;++j){
    	f[i][j]=max(f[i][j],f[i][j-c[cur]]+w[cur]);
    

    ​ 再来考虑 (i) 不被买的情况。因为 (i) 不被买,所以 (i) 子树内的点都不能被买,所以此时直接由子树外的最优值转移过来。

    int cur=ind[i];
    for(int j=0;j<=m;++j)
    	f[i][j]=max(f[i][j],f[oud[cur]+1][j]);//最大值直接转移过来
    

    ​ 此时 (i) 节点的转移就完毕了。由于我们选择的是两种对立事件,且 f[oud[cur]+1][] 和最后转移之前的 f[i][] 分别是这两种对立事件的最优情况,所以可以且只能取 (max) 来继承。 至此,我们又得到了一种 (mathcal{O}(n imes m)) 的做法。

    解法之间的联系

    ​ 从本质上来说,泛化物品解法和DFS序解法都是将当前情况分割为两种 对立事件 (选/不选当前子节点),并将两种对立事件都推演出最优的方案,再直接取 (max) 来合并。这种思想对于树形依赖背包有用,可能对于其他的某些题目同样也是有参考价值的。

  • 相关阅读:
    【HDU1698】 Just a Hook 【线段树入门】
    【转载】线段树 区间合并 小结
    Codeforces 1138B(列方程枚举)
    Codeforces 1132G(关系转化树+dfn+线段树)
    Codeforces 1132E(转化+dp)
    Codeforces 1132D(二分模拟)
    Codeforces 1131G(dp)
    洛谷1941(dp)
    洛谷2758(字符串dp)
    Codeforces 1143B(思维、技巧)
  • 原文地址:https://www.cnblogs.com/parauni-blog/p/13193603.html
Copyright © 2020-2023  润新知