树形dp,在没有写过之前还以为十分的难,其实掌握了基本的几种之后还是比较容易的。
还是回到做dp最难的一个步骤,如何定义状态?
我们对于一颗树,无非考虑的问题有几个,当前的根是谁?它的左右孩子如何处理?我们探讨一下这个几个问题。
我将用题目来一起代入,算是树形dp入门题了。
先来一道普适性最高的题目
洛谷P2015 二叉苹果树
首先我们定义F[ x ][ i ]表示当前为x 作为根节点,在这个根节点下面留住 i 条树枝 的所能留住的最多苹果的数量,则答案就是F[ 1 ][ m ].
那么我们怎么实现呢?
对于当前点,我们肯定是要知道子节点的最佳状态是多少才能做出抉择,那么我们只能递归这颗树,自下往上进行操作。
对于叶子节点,F 值就为0;那么对于一个不为叶子节点,有孩子的点怎么做出抉择呢?
我们考虑要在递归的时候算出每一个节点下面有多少个孩子,然后进行枚举考虑要留住多少个孩子,当然如果孩子数目小于要留住的m个枝条数目,那么只用枚举到孩子的数目;同理,留住的m个枝条数目大于孩子的数目。
于是我们可以写出 F[ x ][ i ]=max(F [ x ] [ i ],F[ x ][ i-j-1 ]+F[ u ][ j ]+value[k]); (u 是当前根节点x的子节点,F[ u ][ j ]表示子节点u留 j 条边的最大值,那么F[ x ][ i ]表示当前根节点留 i 条边的最大值,i>j)
#include <bits/stdc++.h> #define maxn 1005 using namespace std; int tot,next[maxn],first[105],zhi[maxn],value[maxn],x,y,v,n,m,i,f[105][maxn],size[105]; void build(int x,int y,int v) { tot++; next[tot]=first[x]; first[x]=tot; zhi[tot]=y; value[tot]=v; } void dfs(int x,int father) { int k,i,j,u; k=first[x]; while (k!=-1) { u=zhi[k]; if (u==father) { k=next[k]; continue; } dfs(u,x); size[x]+=size[u]+1; for (i=min(size[x],m);i>=0;i--) for (j=min(size[u],i-1);j>=0;j--) f[x][i]=max(f[x][i],f[x][i-j-1]+f[u][j]+value[k]); k=next[k]; } } int main() { cin>>n>>m; memset(first,-1,sizeof(first)); for (i=1;i<=n-1;i++) { cin>>x>>y>>v; build(x,y,v); build(y,x,v); } dfs(1,0); cout<<f[1][m]<<endl; return 0; }
洛谷P1352 没有上司的舞会
这道就比较简单了
设
f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值
f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值
需要注意的是 上司参加了舞会,下属一定不能参加;而上司不参加舞会,下属不是一定要参加舞会,因为数据有负值出现
状态转移方程: f[x][1]+=f[u][0];
f[x][0]+=max(f[u][0],f[u][1]);
#include <bits/stdc++.h> #define maxn 6005 using namespace std; int tot,next[maxn*10],first[maxn],zhi[maxn*10],rong[maxn],i,n,f[maxn][2],l,k; void build (int x,int y) { tot++; next[tot]=first[x]; first[x]=tot; zhi[tot]=y; rong[y]++; } void dfs(int x) { int k,u; k=first[x]; while (k!=-1) { u=zhi[k]; dfs(u); f[x][1]+=f[u][0]; f[x][0]+=max(f[u][0],f[u][1]); k=next[k]; } } int main() { scanf("%d",&n); memset(first,-1,sizeof(first)); for (i=1;i<=n;i++) scanf("%d",&f[i][1]); for (i=1;i<=n;i++) { scanf("%d%d",&l,&k); if (l!=0) build(k,l); } for (i=1;i<=n;i++) if (rong[i]==0) { dfs(i); cout<<max(f[i][0],f[i][1]); } return 0; }
这两道算是最基本的树形dp模型了,我在网上看到还有高级的树形dp,比如换根dp等等,但是难度有点大,故没有涉及。