树形DP:
在树上进行dp,树是递归的定义的,所以树形dp也是递归的求解,一般而言,dp[node]表示的是以node为根的子树能得到的最优解。
一般而言,dp[node]需要从node的子结点进行状态转移,
树形dp又常常和背包结合起来,因为dp[node]的状态是由它的儿子转移而来。
我们常常可以将node的n个儿子看做n个物品,要如何对这n个物品抉择得到最优的dp[node]就常用到背包的思想。
当然,常常dp不止node这一维,至于第二维保存什么 则根据题目来,
一般都是题目给出的限制条件作为dp数组的第二维,然后根据题意保存好状态,写出状态转移方程,
然后递归的求解dp[root]
还有,树形DP还涉及到建图的问题,如果题目能很清晰的输入一个树最好
用vector<int>s[N],s[i]保存节点i的所有儿子
而如果没有的话,保存之后的s会出现j是i的儿子同时i也是j的儿子的情况,这样的情况只需要在递归的过程中
对于当前节点的父亲做标记,枚举其儿子的时候跳过父亲即可(例如下面的第1题)
Problems:
前五题都是二维的dp,需要想好状态转移方程,且大多是树上的背包问题
后五题都是一维的dp,在树上删点或者删边的问题,相对而言后五题更简单
不过第10题给出的图不是树,需要tarjan缩点处理成一棵树
1.POJ 2486 Apple Tree
dp[i][j][0]表示从i节点出发最后回到i节点花费最多j步能获得的最大值,
dp[i][j][1]表示从j节点出发最后不回到i节点花费最多j步能获得的最大值
dp[root][j][0] = max(dp[root][j-k][0] + dp[son][k-2][0])
//root->son 和 son->root 共花费2步,j-k是在其他儿子花费的步数
dp[root][j][1] = max(dp[root][j-k][0] + dp[son][k-1][1])
dp[root][j][1] = max(dp[root][j-k][1] + dp[son][k-2][0])
//只需要回到root一次,一种情况是先走其他儿子回到root再走son,
//另一种情况是先走son这个儿子并回到root再去走其他儿子
#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
#define Sint(n) scanf("%d",&n)
#define Sll(n) scanf("%I64d",&n)
#define Schar(n) scanf("%c",&n)
#define Sint2(x,y) scanf("%d %d",&x,&y)
#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)
#define Pint(x) printf("%d",x)
#define Pllc(x,c) printf("%I64d%c",x,c)
#define Pintc(x,c) printf("%d%c",x,c)
using namespace std;
typedef long long ll;
int dp[111][222][2];
/*
dp[i][j][0]表示从i节点出发最后回到i节点花费最多j步能获得的最大值,
dp[i][j][1]表示从j节点出发最后不回到i节点花费最多j步能获得的最大值
dp[root][j][0] = max(dp[root][j-k][0] + dp[son][k-2][0])
//root->son 和 son->root 共花费2步,j-k是在其他儿子花费的步数
dp[root][j][1] = max(dp[root][j-k][0] + dp[son][k-1][1])
dp[root][j][1] = max(dp[root][j-k][1] + dp[son][k-2][0])
//只需要回到root一次,一种情况是先走其他儿子回到root再走son,
//另一种情况是先走son这个儿子并回到root再去走其他儿子
*/
vector<int>s[111];
int k;
void treedp(int node,int fa)
{
for (int i = 0;i < s[node].size();++i)//从节点node出发
{
int &son = s[node][i];
if (son == fa) continue;
treedp(son,node);
for (int j = k;j >= 0;--j)//枚举node节点所有可能花费的步数
{
for (int ot = 0;ot <= j;++ot)//枚举在son这个儿子上花费的步数
{
if (ot-2>=0) dp[node][j][0] = max(dp[node][j][0],dp[node][j-ot][0]+dp[son][ot-2][0]);
if (ot-1>=0) dp[node][j][1] = max(dp[node][j][1],dp[node][j-ot][0]+dp[son][ot-1][1]);
if (ot-2>=0) dp[node][j][1] = max(dp[node][j][1],dp[node][j-ot][1]+dp[son][ot-2][0]);
}
}
}
}
int main()
{
int n;
while (Sint2(n,k) == 2)
{
mem(dp,0);mem(s,0);
for (int i = 1,v;i <= n;++i)
{
Sint(v);
for (int j = 0;j <= k;++j)
{
dp[i][j][0] = dp[i][j][1] = v;
}
}
for (int i = 1,x,y;i < n;++i)
{
Sint2(x,y);
s[x].push_back(y);
s[y].push_back(x);
}
treedp(1,0);/