树形DP
树形 DP
一、简单树形DP
树形 (DP),即在树上进行 (DP),一般以递归进行,以板题为例:没有上司的舞会。
(Rightarrow) 分析:对于一个结点 (x),其一个儿子结点为 (y),分两种情况:
(1)如果 (x) 不去,那么他的儿子结点可去可不去,即:
(2)如果 (x) 去,那么他的儿子结点只能不去,即:
(Rightarrow) 代码实现:
(1)输入的时候输入的值相当于最开始的 (f_{i,1})。
(2)利用 (dfs) 递归记录每个结点的值即可。
(3)(dfs) 要从根节点开始,此时我们只要根据输入来判断,对于每一个 (l_i) 肯定不是根节点,所以最后看哪一个点没有在 (l_i) 中出现过即是根节点。
点击查看代码
#include <iostream>
#include <cstdio>
#define N 6003
using namespace std;
int n,f[N][2],jilu[N],vis[N];
struct edge{
int next,to;
}edge[N];
int head[N],cnt;
void add(int from,int to){
edge[++cnt].next = head[from];
edge[cnt].to = to;
head[from] = cnt;
}
void Input(){
scanf("%d",&n);
for(int i = 1;i <= n;i ++) scanf("%d",&f[i][1]);
for(int i = 1,l,k;i < n;i ++){
scanf("%d%d",&l,&k);
add(k,l);jilu[l] = 1;
}
}
void dfs(int x){
vis[x] = 1;
for(int i = head[x];i;i = edge[i].next){
int y = edge[i].to;
if(vis[y]) continue;
dfs(y);
f[x][1] += f[y][0];
f[x][0] += max(f[y][1],f[y][0]);
}
}
void work(){
for(int i = 1;i <= n;i ++){
if(!jilu[i]){
dfs(i);
printf("%d
",max(f[i][0],f[i][1]));
return ;
}
}
}
int main(){
Input();
work();
return 0;
}
二、树上背包
其实也就是树形 (DP) 和背包的结合啦。
还是以题为例:
选课
(Rightarrow) 分析:每一门功课都最多有一门先修课,所以可以看成一个树形结构。
考虑如何实现状态转移,令 (f_{x,i,j}) 表示对于结点 (x),已经遍历了其前 (i) 个叶子结点,共学习 (j) 门功课可以获得的最大学分。
从三维的角度分析,我们令每个结点的叶子结点所有的编号依次递增,那么:
其中 (y) 表示 (x) 的第 (i) 个叶子结点,(siz[y]) 表示 (y) 的子树的大小。
如果卡空间怎么办?——考虑可否去掉一维。
刚刚的问题其实就想到于对于一个结点的贡献进行 (01) 背包来计算,所以类似 (01) 背包一样,可以去掉第二维,即:
(Rightarrow) 代码实现:
(1)输入的时候相当于输入最开始时候的 (f_{i,1})。
(2)可以根据题意,把 (0) 作为根节点,但是处理的时候要注意:一共要取 (m + 1) 门功课,因为 (0) 必须的选。
点击查看代码
#include <iostream>
#include <cstdio>
#define N 305
using namespace std;
int n,m,f[N][N],siz[N],vis[N];
struct edge{
int next,to;
}edge[N];
int head[N],cnt;
void add(int from,int to){
edge[++cnt].next = head[from];
edge[cnt].to = to;
head[from] = cnt;
}
void Input(){
scanf("%d%d",&n,&m);
for(int i = 1,k;i <= n;i ++) {
scanf("%d%d",&k,&f[i][1]);
add(k,i);
}
}
void dfs(int x){
vis[x] = 1;
for(int i = head[x];i;i = edge[i].next){
int y = edge[i].to;
if(vis[y]) continue;
dfs(y);
for(int j = m + 1;j >= 1;j --){
for(int k = 1;k <= j;k ++){
f[x][j] = max(f[x][j],f[x][k] + f[y][j - k]);
}
}
}
}
void work(){
dfs(0);
printf("%d",f[0][m + 1]);
}
int main(){
Input();
work();
return 0;
}
二叉苹果树
(Rightarrow) 分析:与上题基本类似,只不过有一个地方不太一样:上题相当于选结点,但是本题相当于选路径,所以在状态转移上有小的差别。
仍然,令 (f_{x,i,j}) 表示对于结点 (x),已经遍历了其前 (i) 个叶子结点,共选 (j) 个树枝可以获得的最多的苹果。
从三维的角度分析,我们令每个结点的叶子结点所有的编号依次递增,那么:
还是可以像 (01) 背包一样,去掉第二维:
(Rightarrow) 代码实现:注意循环顺序已经循环的端点即可。
点击查看代码
#include <iostream>
#include <cstdio>
#define N 105
using namespace std;
int n,Q,vis[N],f[N][N];
struct edge{
int next,to,dis;
}edge[N << 1];
int head[N],cnt;
void add(int from,int to,int dis){
edge[++cnt].next = head[from];
edge[cnt].to = to;
edge[cnt].dis = dis;
head[from] = cnt;
}
void Input(){
scanf("%d%d",&n,&Q);
for(int i = 1,u,v,w;i < n;i ++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
}
void dfs(int x){
vis[x] = 1;
for(int i = head[x];i;i = edge[i].next){
int y = edge[i].to;
if(vis[y]) continue;
dfs(y);
for(int j = Q;j >= 1;j --){
for(int k = 0;k < j;k ++){
f[x][j] = max(f[x][j],f[x][k] + f[y][j - k - 1] + edge[i].dis);
}
}
}
}
void work(){
dfs(1);
printf("%d",f[1][Q]);
}
int main(){
Input();
work();
return 0;
}
相关文章
- 【技术种草】cdn+轻量服务器+hugo=让博客“云原生”一下
- CLB运维&运营最佳实践 ---访问日志大洞察
- vnc方式登陆服务器
- 轻松学排序算法:眼睛直观感受几种常用排序算法
- 十二个经典的大数据项目
- 为什么使用 CDN 内容分发网络?
- 大数据——大数据默认端口号列表
- Weld 1.1.5.Final,JSR-299 的框架
- JavaFX 2012:彻底开源
- 提升as3程序性能的十大要点
- 通过凸面几何学进行独立于边际的在线多类学习
- 利用行动影响的规律性和部分已知的模型进行离线强化学习
- ModelLight:基于模型的交通信号控制的元强化学习
- 浅谈Visual Source Safe项目分支
- 基于先验知识的递归卡尔曼滤波的代理人联合状态和输入估计
- 结合网络结构和非线性恢复来提高声誉评估的性能
- 最佳实践丨云开发CloudBase多环境管理实践
- TimeVAE:用于生成多变量时间序列的变异自动编码器
- 具有线性阈值激活的神经网络:结构和算法
- 内网渗透之横向移动 -- 从域外向域内进行密码喷洒攻击