树型DP

DFS的回溯是树形DP的重点以及核心,当回溯结束后,root的子树已经被遍历完并处理完了。这便是树形DP的最重要的特点

自己认为应该注意的点

  1. 好多人都说在更新当前节点时,它的儿子结点都给更新完了,实际上这并不准确。对于当前节点,我们需要dfs它的儿子,并且在dfs中进行dp。在此过程中并不是等到儿子都更新完我们才更新当前节点的信息(假设当前节点为x, 有儿子son1 , son2, son3, 且son1已经更新完了, 即x已有了son1的信息, son2刚刚更新完,即dfs正在son2的位置回溯), 我们拿着son2, x和son1的信息再次更新x, 如此,x才有了son1,son2的综合信息,之后再从son3 dfs进去找son3的信息,最后才得到x的信息。
  2. 需要注意枚举的顺序,树型dp有点像01背包,而01背包更新信息时你如果没有记录对于i,i中前j个的状态,你就需要倒序枚举,而树型dp中通常直接用f[x] [...]...表示x所在子树的信息,这里枚举k的时候就需要像01背包一样了,从size[x]逆序枚举。

01背包倒着写时要倒序不然就表示可以多次使用前面的物品更新后面的物品的状态(比如你顺着写,你第j个物品用到了前j个物品来更新,那当你再用第j个物品更新第j+1个物品时,又把前面的算了一遍,所以就算重复了),这不就成了完全背包嘛,我们的01背包倒着写是为了每次更新,都是取用它前面的状态,而它前面的状态又是没改过的,所以不会选重。树型dp同理,用前j个儿子更新第j+1个儿子时不能选重复)

梨提

luoguP1352 没有上司的舞会(熟悉一下回溯)

https://www.luogu.org/problemnew/show/P1352

/*

f[i] [0/1] 0与1分别表示第i个人不去,去时的最大快乐指数

所以 f[i] [0] += max( f[son] [1] ,f[son] [0]);

f[i] [1] += max(f[son] [0] , 0)

  1. 为什么f[i][1] 要与0 做比较? : 因为快乐指数可能是负的

  2. 为什么f[i][0] 不用与0作比较,直接=max?:就算是负的,也只能直接去最大的,他又不去... 

  3. 为什么是+=? : 下面有

*/

#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 6000+9; int n,cnt;
int head[MAXN];
int R[MAXN],f[MAXN][2],fa[MAXN]; struct edge{
int y,next;
}e[MAXN]; void add_edge(int x, int y) {
e[++cnt].y = y;
e[cnt].next = head[x];
head[x] = cnt;
} void dfs(int now) {
f[now][1] = R[now];
f[now][0] = 0;//初始化
for(int i = head[now]; i; i = e[i].next ) {//i是边编号
int nn = e[i].y ;
dfs(nn);//先递归进去处理儿子的f值
f[now][0] += max(f[nn][1] ,f[nn][0]) ;
f[now][1] += max(f[nn][0] , 0);
//注:因为一棵树可能有多个儿子,所以这里都是+=;
}
} int main() {
scanf("%d",&n);
for(int i = 1; i <= n; i++) {
scanf("%d",&R[i]);
}
int l,k;
for(int i = 1; i <= n; i++) {
scanf("%d%d",&l,&k);
if(i == n) break;//只输入了n-1行
fa[l] = k;
add_edge(k,l);
}
int root;
for(int i = 1; i <= n; i++) if(!fa[i]) {
root = i;
break;//找根
}
dfs(root);
int ans = max(f[root][0],f[root][1]);
printf("%d",ans);
}

luoguP2014 选课(注意树型dp要符合dp的特点)

https://www.luogu.org/problem/P2014

注意枚举当前节点所选的课要倒序枚举,原因同上

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX = 300+9; int n,m;
int f[MAX][MAX], arr[MAX], size[MAX];
//f[i][j]表示子树i中(包括i)选j门课的最大学分 struct edge{
int y, next;
}e[MAX<<1];
int head[MAX], cnt;
void add_edge(int x, int y) {
e[++cnt].y = y;
e[cnt].next = head[x];
head[x] = cnt;
} void dfs(int x) {
size[x] = 1;
for(int i = head[x]; i; i = e[i].next) {
dfs(e[i].y);
size[x] += size[e[i].y];
for(int k = size[x]; k >= 1; k--) {//父亲不选就都不能选,所以>=1//这儿的k必须倒序
for(int j = 0; j < k; j++) {//枚举在当前儿子中选的课
f[x][k] = max(f[x][k],f[x][k-j] + f[e[i].y][j]);
}
}
}
} int main() {
scanf("%d%d",&n,&m);
int x;
for(int y = 1; y <= n; y++) {
scanf("%d%d",&x,&arr[y]);
add_edge(x,y);
}
m++;
// f[0][1] = 0;
for(int i = 1; i <= n; i++) f[i][1] = arr[i];
dfs(0);
printf("%d",f[0][m]);
}

luoguP2015 二叉苹果树

注: 因为相当于0-1背包中的选或不选,所以 j 是逆序的,其他细节在代码里有体现和解释

https://www.luogu.org/problemnew/show/P2015

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 100+9; int n,Q;
int f[MAXN][MAXN];//f[i][j]表示: 点i和它的子树保留j个树枝时的最大苹果数
int head[MAXN],cnt; struct edge{
int y,val,next;
}e[MAXN]; void add_edge(int x, int y, int val) {
e[++cnt].y = y;
e[cnt].val = val;
e[cnt].next = head[x];
head[x] = cnt;
} void dfs(int now ,int dad) {
f[now][0] = 0;//初始化边界
for(int i = head[now]; i; i = e[i].next ) {
int nn = e[i].y ;
if(nn == dad) continue ;//应该是continue吧,不是return ;
dfs(nn, now);//先递归进去处理儿子的f值
for(int j = Q; j; j--) {//逆序的原因: 0-1背包选或不选
for(int k = 0; k < j; k++) {//枚举 左/右 子树保留的树枝
f[now][j] = max(f[now][j], f[now][j-k-1] + f[nn][k] + e[i].val );
//要选子树nn上边,就要把子树nn与根的边选上,所以这里是j-k还要"-1"
}
}
}
} int main() {
scanf("%d%d",&n,&Q);
int m = n-1;//二叉树的边
for(int i = 1, x, y, val; i <= m; i++) {
scanf("%d%d%d",&x,&y,&val);
add_edge(x,y,val);
add_edge(y,x,val);//只是描述了边,但不知道父亲是谁,儿子是谁,所以建双向的
//所以下面的dfs要开一个树根的形参,防止死循环
}
//1为根
dfs(1,1);
printf("%d",f[1][Q]);
return 0;
}

luoguP1270 “访问”美术馆

初学树型dp的更多相关文章

  1. POJ3659 Cell Phone Network(树上最小支配集:树型DP)

    题目求一棵树的最小支配数. 支配集,即把图的点分成两个集合,所有非支配集内的点都和支配集内的某一点相邻. 听说即使是二分图,最小支配集的求解也是还没多项式算法的.而树上求最小支配集树型DP就OK了. ...

  2. POJ 3342 - Party at Hali-Bula 树型DP+最优解唯一性判断

    好久没写树型dp了...以前都是先找到叶子节点.用队列维护来做的...这次学着vector动态数组+DFS回朔的方法..感觉思路更加的清晰... 关于题目的第一问...能邀请到的最多人数..so ea ...

  3. 【XSY1905】【XSY2761】新访问计划 二分 树型DP

    题目描述 给你一棵树,你要从\(1\)号点出发,经过这棵树的每条边至少一次,最后回到\(1\)号点,经过一条边要花费\(w_i\)的时间. 你还可以乘车,从一个点取另一个点,需要花费\(c\)的时间. ...

  4. 洛谷P3354 Riv河流 [IOI2005] 树型dp

    正解:树型dp 解题报告: 传送门! 简要题意:有棵树,每个节点有个权值w,要求选k个节点,最大化∑dis*w,其中如果某个节点到根的路径上选了别的节点,dis指的是到达那个节点的距离 首先这个一看就 ...

  5. 【POJ 3140】 Contestants Division(树型dp)

    id=3140">[POJ 3140] Contestants Division(树型dp) Time Limit: 2000MS   Memory Limit: 65536K Tot ...

  6. Codeforces 581F Zublicanes and Mumocrates(树型DP)

    题目链接  Round 322 Problem F 题意  给定一棵树,保证叶子结点个数为$2$(也就是度数为$1$的结点),现在要把所有的点染色(黑或白) 要求一半叶子结点的颜色为白,一半叶子结点的 ...

  7. ZOJ 3949 (17th 浙大校赛 B题,树型DP)

    题目链接  The 17th Zhejiang University Programming Contest Problem B 题意  给定一棵树,现在要加一条连接$1$(根结点)和$x$的边,求加 ...

  8. BZOJ 1564 :[NOI2009]二叉查找树(树型DP)

    二叉查找树 [题目描述] 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小. 另一方面,这棵查找树中每个结点都有一个权值,每个结 ...

  9. Codeforces 149D Coloring Brackets(树型DP)

    题目链接 Coloring Brackets 考虑树型DP.(我参考了Q巨的代码还是略不理解……) 首先在序列的最外面加一对括号.预处理出DFS树. 每个点有9中状态.假设0位不涂色,1为涂红色,2为 ...

随机推荐

  1. PM8909 linear charger硬件概述

    电池充电是由qpnp-vm-bus.c(电池驱动BMS)和qpnp-linear-charger.c(线性充电器)组成: SMMB charger:Switch-ModeBattery Charger ...

  2. 爬取编程常用词汇,保存为Excel

    编程常用词汇 import requests import openpyxl from lxml import etree import re url = 'https://www.runoob.co ...

  3. 如何在文本编辑器中实现搜索功能? 字符串比较算法 BF算法 RK算法

    1.暴力比较 BF算法 2.比较字串hash值 RK算法 //字符串匹配 public class StringCmp { //约定:A主串长 n ,B模式串 长m.要求:在A串中找到B串匹配的下标 ...

  4. sqliteman

    2.安装文件 采用源码方式安装 可用下面地址自行下载 https://sourceforge.net/projects/sqliteman/files/sqliteman/1.2.2/ 3.安装 1) ...

  5. CF1244C The Football Season

    题目链接 problem 给定\(n,p,w,d\),求解任意一对\((x,y)\)满足\[xw+yd=p\\ x + y \le n\] \(1\le n\le 10^{12},0\le p\le ...

  6. Sharding-JDBC:单库分表的实现

    剧情回顾 前面,我们一共学习了读写分离,垂直拆分,垂直拆分+读写分离.对应的文章分别如下: Sharding-JDBC:查询量大如何优化? Sharding-JDBC:垂直拆分怎么做? 通过上面的优化 ...

  7. python--8大排序(原理+代码)

    常用的排序方法:冒泡排序.选择排序.插入排序.快速排序.堆排序.归并排序 冒泡排序(Bubble Sort): 比较相邻的元素.如果第一个比第二个大(升序),就交换他们两个. 对每一对相邻元素作同样的 ...

  8. JAVA 网络编程 - 实现 群聊 程序

    在实现 这个 程序之前, 我们 需要 了解 一些 关于 Java 网络 编程 的 知识. 基本 的 网络知识: 网络模型 OSI (Open System Interconnection 开放系统互连 ...

  9. oracle视图和索引

    视图和索引 视图 视图的作用 控制数据访问.简化查询.避免重复访问相同的数据 视图的优点 限制用户只能通过视图检索数据,用户看不到底层基表 注意事项 视图可以理解为临时表,会随着真实表的数据变化而自动 ...

  10. 细数使用View UI(iView)开发中遇到的坑

    一.前言 View UI,即原先的 iView,是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品. 官网地址:https://www.iviewui.com/docs ...