代码工具(以二叉苹果树为例)
二叉树
int dfs(int u, int i) {
if (dp[u][i]) return dp[u][i]; // u 是节点 , i 是变量情况 , 参考具体的例题
for (int k = 0; k < j; k++) { // 枚举第二个参数i的分配情况
dp[u][i] = max(dp[u][i], dfs(u.lson, k) + dfs(u.rson, j - k));
}
return dp[u][i];
}
多叉树(递归分担)
int dfs(int u, int father) {
for (int i = 0; i < edge[u].size(); i++) { // 枚举u节点的每一个孩子 (节点)
// 得到这个树枝的权值和末顶点
int v = edge[u][i].v, w = edge[u][i].w;
// 不回头搜索父亲 因为存多叉树的时候是用vector存无向边的
if (v == father) continue;
// 递归到最深的叶子节点,然后返回,为了后面的sum做准备
dfs(v, u);
// 统计这个点有多少个边
sum[u] = sum[v] + 1;
// 从大边开始遍历到小边(必须)类比背包问题,就是说一个边只能删除一次
for (int j = sum[u]; j >= 0; j--) {
// 遍历
for (int k = 0; k <= j - 1; k++) {
// 保留效果要么是以前的,要么是根的其他子树保留j - k - 1条,v子树保留k条。
dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + w); // w 是v - u这条边的权值,或苹果数目
}
}
}
}
原理:
在树这种数据结构上做DP很常见:
给出一棵树,要求以最少代价(或最大收益)完成给定的操作。在树上做 DP显得非常自然,因为树本身有“子结构”性质(树和子树),具有递归性,符合5.1.2节提到的“记忆化递归”的思路,所以树形 DP一般就这样编程。
基于树的解题步骤一般是先把树转换为有根树(如果是几棵互不连通的树,就加一个虚拟根,它连接所有孤立的树),然后在树上做DFS,递归到最底层的叶子节点,再一层层返回信息更新至根节点。显然,树上DP所操作的就是这一层层返回的信息。不同的题目需要灵活设计不同的DP状态和转移方程。
核心:
树的搭建
// 没有上司的酒会 创建树代码
void addedge(int a, int b) {
G[from].push_back(to);
father[to] = from;
}
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
addedge(v, u);
}
使用递归(从根开始,先搜索下面的,再操作上面的)
// 没有上司的酒会 部分代码
void dfs(int u) {
// 初始化先
dp[u][0] = 0;
dp[u][1] = val[u];
// 遍历孩子
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
// 一定要先搜索孩子,然后再操作这颗树的, 孩子是基础
dfs(v);
dp[u][i] += dp[v][0]; //父结点选择,子节点不选
dp[u][0] += max(dp[v][0], dp[v][1]); // 父结点不选,子节点随意
}
}
寻找根节点
// 根的father为0
int t = 1;
while(father[t]) t = father[t];
题目:
1、二叉苹果树 洛谷p2015
2、没有上司的舞会 洛谷p1352
3、有线电视网 洛谷p1273 poj1155
经典题:hdu 1520 2196
树形背包:poj 1947 2486 hdu 1011 1561 4003
洛谷 4322 1352 1040 1122 1273 2014 2585 3047 3698 5658 2607 3477 4395 4516