树上背包
树上背包
树上背包的模板 P2014 [CTSC1997] 选课
有 \(n\) 个节点,背包大小 \(m\) ,求最大价值。
树上背包是有依赖的背包。只有选择了父亲,才能选择儿子。既然是树上的,自然设出 \(f_{i,j}\) 表示在以 \(i\) 为根节点的子树内选择了大小为 \(j\) 的课,最大价值为 \(f\) 。
Code:
void dfs(int x){
f[x][1]=c[x];
for(auto i:v[x]){
dfs(i);
for(int j=m;j>=1;j--){
for(int k=0;k<=j-1;k++){
f[x][j]=max(f[x][j],f[x][j-k]+f[i][k]);
}
}
}
}
每个节点都会作为儿子访问一遍。为了合并已经处理的 \(x\) 和当前的子树 \(i\) ,时间是 \(O(m^2)\) 。总共是 \(O(nm^2)\) 。有更快的解法。
可以计算 \(size\) 。进入子树后,限定循环的范围,时间复杂度可以到 \(O(nm)\) 。但是这个方法适用性不好,改一改每个节点的物品大小就不行了。
有更加巧妙的做法。利用 dfs序,将树上的变为线性的结构。要按 dfs序倒序枚举,才能先枚举儿子在枚举父亲,符合要求。此时设 \(f_{i,j}\) 表示在 dfs序为 \(i \sim n\) 中选择 \(j\) 的最大价值。我们对每个节点讨论:
- 如果不选当前节点。那么它的子树内都不能选了。\(f_{i,j}=f_{i+siz_i,j}\)
- 如果选,就像0/1背包那样选取。\(f_{i,j}=f_{i+1,j-1}+c_i\)
Code:
int cnt,dfn[N],fid[N];
int siz[N];
int f[N][N];
void dfs(int x){
dfn[x]=++cnt;
fid[cnt]=x;
siz[x]++;
for(auto i:v[x]){
dfs(i);
siz[x]+=siz[i];
}
}
for(int i=n+1;i>=1;i--){
for(int j=1;j<=m;j++){
int x=fid[i];
f[i][j]=max(f[i+siz[x]][j],f[i+1][j-1]+c[x]);
}
}
此时就将树形依赖的背包转化为由 dfs序下标并限定选取的0/1背包。这样做避免了两个子树信息的合并,转而求之前信息与当前节点的合并,时间复杂度优化到了 \(O(nm)\) 。
例1:P2515 [HAOI2010] 软件安装
问题:现在我们的手头有 \(N\) 个软件,对于一个软件 \(i\),它要占用 \(W_i\) 的磁盘空间,它的价值为 \(V_i\)。我们希望从中选择一些软件安装到一台磁盘容量为 \(M\) 计算机上,使得这些软件的价值尽可能大(即 \(V_i\) 的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件 \(i\) 只有在安装了软件 \(j\)(包括软件 \(j\) 的直接或间接依赖)的情况下才能正确工作(软件 \(i\) 依赖软件 \(j\))。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 \(0\)。
我们现在知道了软件之间的依赖关系:软件 \(i\) 依赖软件 \(D_i\)。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则 \(D_i=0\),这时只要这个软件安装了,它就能正常工作。
第 1 行:\(N,M(0\leq N\leq 100, 0\leq M\leq 500)\)
第 2 行:\(W_1,W_2, ... W_i, ..., W_n (0\leq W_i\leq M)\)
第 3 行:\(V_1, V_2, ..., V_i, ..., V_n (0\leq V_i\leq 1000)\)
第 4 行:\(D_1, D_2, ..., D_i, ..., D_n (0\leq D_i\leq N, D_i≠i)\)
缩点+树型dp
分析:\(n\) 条边,是基环树森林。可能构成环。要先缩点,因为只有 \(n\) 条边,将缩点后所有入度为0的点与0号点相连后会构成一棵树。做树上背包。
for(int i=cnt1;i>=1;i--){
for(int j=m;j>=0;j--){
int x=idn[i];
f[i][j]=max(f[i][j],f[i+siz[x]][j]);
if(j>=w1[x])f[i][j]=max(f[i][j],f[i+1][j-w1[x]]+c1[x]);
}
}

浙公网安备 33010602011771号