树型DP:二叉苹果树
另外一个提交地址:http://www.cqoi.net:2012/JudgeOnline/problem.php?id=1375
里面有中文题目,不解释题目了。
树型DP:主要是两点,怎么建树,怎么DP。这两者应该说是相互制约相互影响的,怎么DP就要怎么建树,而建树方法对不对也决定了你能不能DP。所以先分析怎么DP的,再分析怎么建树
题意说要保留m个树枝,而且注意权值不是点的而是树枝的,这样子并不利于我们解决问题,我们可以转化一下,把权值转移给节点。首先给出所有边的信息后,我们的建树是唯一的(这个问题值得思考一下为什么,唯一是指不考虑左右孩子的位置,而只考虑从属关系,好像1(3,4),1(4,3)我们认为是同一棵树,这样考虑的话,建树是唯一的)
基于树的递归性质,而且规定了1必定为树根,而建树又是唯一的,那么我们就可以采用递归的方法用1开始建树,建树部分不详说,看代码就知道是怎么回事了。另外建树时我们要把树枝的权值给节点,而一个树枝连接的其实是孩子和父亲,我们把权值给孩子。这样建树后,每个节点都会有权值,除了1,因为是整个树的根它没有父亲,所以它的权值为0.注意其他点的权值也可能为0,因为本来树枝权值可能为0,但是这不影响解题
细想一下,一棵树中要保留m个树枝,等价于要保留m+1个点,其中一个点就是树根,即要保留m个子孙后代节点
所以问题转化为,以1为根的树,包括1在内一共保留m+1个节点,或说以1为根保留m个子孙后代节点。注意这两者的本质是相同的,但是写法却不同,推荐前者的写法,不容易出错。下面来说说两者具体都是怎么DP的
两者都是递归式DP,即记忆化搜索
先说第一种:dp[rt][m]表示以rt为根,保留m个节点(包括rt自己),所以目标状态为dp[1][m+1]
状态转移方程为 dp[rt][m] = max{ dp[lch][a] + dp[rch][b] } + val[rt];
解释:即以左孩子为根保留a个节点(包括左孩子本身),以右孩子为根保留b个节点(包括右孩子本身),最后要加上rt这个点本身的权值。可知 a+b+1=m (注意这点)
注意:rt=0时,即递归边界,返回0,同样的,m=0时返回0,即一个点都不保留(包括根自己)
第二种:dp[rt][m]表示以rt为根,保留m个子孙后代节点,所以目标状态为dp[1][m]
状态转移方程: dp[rt][m] = max { dp[lch][a]+val[lch] + dp[rch][b]+val[rch] } + val[rt]
解释:即以左孩子为根保留a个子孙后代节点,而左孩子本身的权值也要计算进去。以右孩子保留b个子孙后代节点,而右孩子本身的权值也要计算进去,最后要加上根的权值
可知 (a+1)+(b+1)+1=m
注意:这样dp需要注意一些细节:rt=0,递归边界,返回0;m=0,返回val[rt],m=0表示不要保留孩子,但是还有根,所以要返回根的权; m<0,在这种DP种是会出现m<0的情况的,<0表示什么都不保留,即根都不要保留,直接删掉整个子树,返回0,因为什么节点都不保留了
先给出第1种和第2种dp代码,方便对比,可以看完全部代码再回头看
第1种
int dfs(int rt ,int m) { if(rt==0 || m<0) return 0; if(m==0) return dp[rt][m]=tree[rt].val; if(dp[rt][m]!=-1) return dp[rt][m]; dp[rt][m]=0; for(int a=0; a<=m; a++) { int b=m-a; int sl,sr,lch,rch; lch=tree[rt].lch; rch=tree[rt].rch; sl=dfs(lch,a-1); sr=dfs(rch,b-1); dp[rt][m]=max(dp[rt][m] , sl+sr); } return dp[rt][m]+=tree[rt].val ; }
第二种
int dfs(int rt ,int m) { if(rt==0 || m<0) return 0; if(m==0) return dp[rt][m]=tree[rt].val; if(dp[rt][m]!=-1) return dp[rt][m]; dp[rt][m]=0; for(int a=0; a<=m; a++) { int b=m-a; int sl,sr,lch,rch; lch=tree[rt].lch; rch=tree[rt].rch; sl=dfs(lch,a-1); sr=dfs(rch,b-1); dp[rt][m]=max(dp[rt][m] , sl+sr); } return dp[rt][m]+=tree[rt].val ; }
全部代码(以第1种DP)
#include <cstdio> #include <cstring> #define N 110 #define INF 0x3f3f3f3f #define max(a,b) a>b?a:b int val[N][N]; int dp[N][N]; struct node { int lch,rch; int val ; }tree[N]; bool vis[N]; void build(int rt ,int n) { vis[rt]=true; for(int i=1; i<=n; i++) if(!vis[i] && val[rt][i]!=-1) { if(!tree[rt].lch) tree[rt].lch=i; else tree[rt].rch=i; tree[i].val=val[rt][i]; build(i,n); } } int dfs(int rt ,int m) { if(rt==0 || m<=0) return dp[rt][m]=0; if(dp[rt][m]!=-1) return dp[rt][m]; dp[rt][m]=0; for(int a=0; a<m; a++ { int b=m-1-a int sl,sr,lch,rch; lch=tree[rt].lch; rch=tree[rt].rch; sl=dfs(lch,a); sr=dfs(rch,b); dp[rt][m]=max(dp[rt][m] , sl+sr); } return dp[rt][m]+=tree[rt].val ; } int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { memset(val,-1,sizeof(val)); for(int i=1; i<n; i++) { int a,b,v; scanf("%d%d%d",&a,&b,&v); val[a][b]=val[b][a]=v; } memset(vis,false,sizeof(vis)); memset(tree,0,sizeof(tree)); build(1,n); //for(int i=1; i<=n; i++) //printf("%d: lch=%d rch=%d val=%d\n",i,tree[i].lch,tree[i].rch,tree[i].val); memset(dp,-1,sizeof(dp)); dfs(1,m+1); printf("%d\n",dp[1][m+1]); } return 0; }