树形 DP 是 NOIP/CSP 常考类型,是最重要的 DP。
由于树固有的递归性质,树形 DP 一般都是递归进行的。
基础
以下面 【LG P1352】没有上司的舞会 为例,介绍一下树形 DP 的一般过程。
某大学有 \(n\) 个职员,编号为 \(1\) ~ \(n\)。他们之间有从属关系,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 \(a_i\),但是,如果某个职员的上司来参加舞会了,那么这个职员就不肯来参加舞会了。求最大的快乐指数。
我们可以定义 \(f_{i,0/1}\) 代表以 \(i\) 为根的子树的最优解(第二维的值为 \(0\) 代表 \(i\) 不参加舞会的情况,\(1\) 代表 \(i\) 参加舞会的情况)。
显然,我们可以推出下面两个状态转移方程(其中下面的 \(v\) 都是 \(u\) 的儿子,下同):
-
\(f_{u,0}=\sum_{\text{edge}(u,v)}\max\{f_{v,1},f_{u,0}\}\)(上司不参加舞会时,下属可以参加,也可以不参加)
-
\(f_{u,1}=\sum_{\text{edge}(u,v)}f_{u,0}+a_i\)(上司不参加舞会时,下属可以参加,也可以不参加)
我们可以通过 DFS,在返回上一层时更新当前结点的最优解。
代码:
#include<bits/stdc++.h>
using namespace std;
int a[6005],f[6005][2];
vector<int> v[6005];
bool staff[6005];
void dp(int x) {
f[x][0]=0;
f[x][1]=a[x];
for(int i=0; i<v[x].size(); i++) {
int t=v[x][i];
dp(t);
f[x][0]+=max(f[t][0],f[t][1]);
f[x][1]+=f[t][0];
}
}
int main() {
int n;
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",a+i);
}
for(int i=1; i<=n; i++) {
int x,y;
scanf("%d %d",&x,&y);
v[y].push_back(x);
staff[x]=1;
}
int root;
for(int i=1; i<=n; i++) {
if(!staff[i]) {
root=i;
}
}
dp(root);
printf("%d",max(f[root][0],f[root][1]));
return 0;
}
相关练习:【LG P2016】战略游戏。
树上背包
树形背包解决的问题是给你几个物品,但物品有依赖关系,\(a\) 依赖 \(b\),\(b\) 依赖 \(c\),选 \(a\) 就必须选 \(b\),选 \(b\) 就必须选 \(c\),一个物品只能依赖一个物品,但一个物品可以被多个物品依赖,这里就能看出来这是一个树。叫你选择 \(n\) 物品可以使得价值最大,价值为多少。
一般我们的状态就是 \(f_{i,j}\) 表示以 \(i\) 为根节点的子树中选择了 \(j\) 个点所得到的价值,转移也大都是利用 dfs 回溯和背包来进行。
以下面 【CTSC 1997】选课 为例,介绍一下树形背包。
有一堆树构成的森林,共 \(n\) 个点。每个点有一个权值 \(s_i\)。一个点可以被选择,当且仅当它到根节点的路径上的所有点都被选择。共选择 \(m\) 个点,求被选择的点的权值和的最大值。
一个小技巧:我们发现,如果 \(0\) 算一个节点的话,整张图就是一棵树了。
这样的好处:
一棵树就不用分别考虑各棵树然后合并了。输入方便很多,不用特别处理 \(0\) 的情况。但是 \(m\) 就会受影响。
因为根节点 \(0\) 是必选的,所以只要让 \(m\) 增加 \(1\) 就好了。
首先,不难看出,父节点的信息可以由子节点合并得到并且不会影响子节点。
所以使用 dp 或者记忆化搜索就好了。
不难想到,用 \(dp_{u,i}\) 表示以节点 \(u\) 为根的子树,选择 \(i\) 个点可以获得的最大权值和。然后想如何转移。
好像遇到麻烦了!
显然合并子节点的信息一定能得到父节点的信息,但使用简单的算法好像不行了。
没事反正数据范围小。
继续观察,发现每个子节点都会占用父节点 \(i\) 的一部分,又有一个贡献,可以选择或不选择。
重量……价值……总重……这不是 \(01\) 背包吗?
不同之处在于,每个子节点的重量都是变量。
重新设计状态,用 \(dp_{u,i,j}\) 表示节点 \(u\) 的前 \(i\) 个子节点,限重为 \(j\) 能得到的最大权值和(价值和)
像 \(01\) 背包一样压缩空间,得到:
\(dp_{i,j}\) 表示节点 \(u\),限重 \(j\) 的最大权值和(价值和)。
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to;
dp(v);
for(int j=m+1; j; j--) {
for(int k=0; k<j; k++) {
f[u][j]=max(f[u][j],f[v][k]+f[u][j-k]);
}
}
}
相关练习:
-
【LG P1273】有线电视网 树上分组背包经典题。
转移方程 \(dp_{i,j}=\max(dp_{i,j},dp_{i,j-k}+dp_{v,k}-w)\)。\(v\) 表示枚举到这一组(即 \(i\) 的儿子),\(w\) 指这条边的边权,\(k\) 表示枚举到这组中的元素:选 \(k\) 个用户。
-
【LG P1272】重建道路 类似树上背包的树形 DP。
递归操作,\(f_{i,j}\) 表示保留 \(i\) 为根的子节点。\(c\) 数组记录点的度。因为这是一棵树,所以每个点的度为 \(1\)。然后随便设一个根,我设的 \(1\) 为根,\(1\) 的根就为 \(0\)。递归时传入两个参数,为当前节点和当前节点的根。
-
【IOI 2005】Riv 河流 树上背包,也有更快的 wqs 二分做法。
挖坑,未完待续……
参考资料
- 树形 DP - OI Wiki
- 【动态规划】树形DP完全详解! - RioTian
- 题解 P1273 【有线电视网】 - Fairycastle - 洛谷博客
- 题解 P1272 【重建道路】 - beretty 的博客 - 洛谷博客
标签:背包,浅谈,int,树形,DP,节点,dp 来源: https://www.cnblogs.com/AFewMoon/p/15484288.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。