[TOC]
# 树形DP
树形 DP,即在树上进行的 DP。考虑树的边和点的取舍问题。.由于树固有的递归性质,树形 DP 一般都是递归进行的。
这样讲可能有点抽象,我们以一道经典树形DP问题为例。
## 没有上司的舞会
[题目连接](https://www.luogu.org/problemnew/show/P1352)
### 题意描述
有一个公司,这个公司的职员关系结构是一颗树,也就是说,一个人最多有一个上司,但可以有多个手下。并且每个人有一个快乐值 。现在,公司要举行一个舞会,但是,没有人想和自己的上司一起参加舞会,现在问你参加舞会的人的快乐值之和最大可以是多少。
### 分析
因为题目保证了职员关系结构是一棵树,而且因为关系是有向的,所以一定有一个点没有上司,这个点也就是树的根。从根出发考虑这个问题,对于当前节点来说,如果我们选择当前节点那么我们就不能选择这个节点的直接后继节点,而如果不选的话,就没有限制了。
### 状态与转移方程
用dp[x](#)表示不选择x这个点,用dp[x](#)表示选择这个点。用set(x)表示x节点的后继节点,也就是x的下手。
那么可得转移方程:
> dp\[x\]\[1\] = \\sum\_{i\\in set(x)} dp\[i\]\[0\]
> dp\[x\]\[0\] = \\sum\_{i\\in set(x)}max(dp\[i\]\[0\], dp\[i\]\[1\])
### 复杂度和代码
时间复杂度:O(n),
空间复杂度:O(n)。
~~~
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 6e3 + 7;
int a[MAXN], fa[MAXN];
vector<int> G[MAXN];
//dp[x][0]表示不包含x,dp[x][1]表示包含x。
int dfs(int x, int t){
int ans = 0;
if(t == 0){
for(auto p: G[x]){
ans += max(dfs(p, 0), dfs(p, 1));
}
} else{
for(auto p: G[x]){
ans += dfs(p, 0);
}
ans += a[x];
}
return ans;
}
int main(int argc, char const *argv[])
{
int n;
while(cin >> n){
for(int i=1;i<=n;++i){
cin >> a[i];
fa[i] = 0;
G[i].clear();
}
int u, v;
while(cin >> u >> v){
if(u || v){
G[v].push_back(u);
fa[u]++;
} else break;
}
int root;
for(int i=1;i<=n;++i) if(fa[i] == 0) root = i; //求根节点
cout << max(dfs(root, 1), dfs(root, 0)) << endl;
}
return 0;
}
~~~
## 习题
[HDU 2196 Computer](http://acm.hdu.edu.cn/showproblem.php?pid=2196)
[POJ 1463 Strategic game](http://poj.org/problem?id=1463)
[POJ 3585 Accumulation Degree](http://poj.org/problem?id=3585)
- 简介
- 零基础快速入门ACM
- C语言快速入门
- C语言快速入门
- C/C++基础
- 输入输出
- 数组和字符串
- 数组
- 字符数组
- 函数和递归
- C++标准容器库(STL)
- Vector
- Map
- Set
- String
- Stack
- Queue
- Priority_queue
- 贪心
- 硬币问题
- 区间调度问题
- 字典序最小问题
- 独木舟问题
- 搜索
- DFS
- BFS
- 剪枝
- 记忆化搜索
- 常用技巧
- 倍增
- 高精度
- 大数加法
- 大数减法
- 大数乘法
- 大数除法
- 大数阶乘
- 输入挂
- 前缀和
- 二分
- 本地预处理
- 尺取
- 枚举
- 坐标离散化
- 分治
- 数列分治
- 树上分治
- 平面分治
- 计算几何
- 凸包
- 点积
- 叉积
- 线段关系
- 皮克定理
- 最近点对
- 数据结构
- 线段树
- 树状数组
- 并查集
- 动态规划
- 基础知识
- 信息学竞赛中的动态规划
- 动态规划初步
- 最长上升子序列(LIS)
- 最长公共子序列(LCS)
- 最大子段和
- 背包问题
- 部分背包
- 0 1 背包
- 完全背包
- 多重背包
- 背包的可行性问题
- 线性DP
- 树形DP
- 区间DP
- 数位DP
- 动态规划优化
- 字符串
- KMP算法
- 拓展KMP
- 字符串Hash
- Manacher算法
- 后缀数组
- Trie树
- 博弈论
- Nim博弈
- Bash博弈
- 斐波那契博弈
- 威佐夫博弈
- SG函数
- 数论
- 整数研究
- 素数判断
- 素数筛选
- 快速幂
- 算数基本定理
- 最大公约数(Gcd)
- 费马小定理
- 扩展欧几里得
- 逆元
- 同余定理
- 组合数学
- 容斥原理
- 抽屉原理
- 卢卡斯(Lucas)
- 卡特兰(Catalan)
- 著名函数
- 欧拉函数
- 莫比乌斯函数
- 线性代数
- 矩阵运算
- 矩阵快速幂
- 图论
- 存图方法
- 邻接矩阵
- 邻接表
- Vector存图
- 链式前向星
- 图的遍历
- 拓扑排序
- 最短路
- Dijkstra算法
- SPFA算法
- Floyed 算法
- 最小生成树
- Prim算法
- Kruskal算法
- 最近公共祖先 (LCA)
- 二分图匹配
- 网络流
- 其他
- 莫队