ural(Timus) 1018. Binary Apple Tree

树型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;

}

 

 

你可能感兴趣的:(binary)