二叉苹果树

来源:互联网 发布:ubuntu登陆界面鼠标 编辑:程序博客网 时间:2024/04/29 19:26

二叉苹果树

//p2015 二叉苹果树 耗时:2017-6-9 2017-6-11归根结底,网络中,抄代码的还是很多。真正按自己理解写代码的不多,尤其能把核心部分写出的更少。
//学习树形动态规划,以此为例
//http://blog.csdn.net/dante__alighieri/article/details/41826385该文写得比较清晰。
//有二叉树基础,建树很快弄懂,但在动态规划上花了很长时间
//为什么是dp(1,q+1);而不是dp(1,q);折腾了好长时间。
//http://www.zzxu.cn/haoju/194259.html此文分析得还可以,摘抄如下:
//【算法&思路】:首先,可以肯定的是,这是一道有关树规的题目,父节点和子节点存在着相互关联的阶段关系。
//第一步完成。再执行第二步:我们观察到题目数据量不大,所以有两种选择:邻接矩阵和邻接表。因为邻接矩阵的代码简单,思路清晰,所以建议能写邻接矩阵的时候就不要写邻接表了。我们设ma[x][y]为边的值,因为树是双向的,所以要再记录ma[y][x]。
//设tree[v,1]为节点v的左子树,tree[v,2]为节点v的右子树,然后我们再递归建树(因为树是递归定义的,所以很多时候建树都要考虑递归)。
//建树的问题解决的了,我们就要列状态转移方程了。根据求什么设什么的原则,我们定义f[i][j]表示以i为节点的根保留k条边的最大值,那么f[v][k]=max(f[v][k],(f[tree[v][1]][i]+f[tree[v][2]][k-i-1]+num[v])),我们枚举i就可以了。正如我开头提到的。因为树是递归定义的所以我们可以用记忆化搜索的形式(dfs)来具体实现。而树本身严格分层,而且没有环。所以是不会重复的。
//F[1][Q+1]就是答案。因为题目中给的是边的权值,而我们在处理时将每条边的权值全赋给其所连的父节点和子节点中的子节点(将关于边的问题转化为关于点的问题),所以最后是Q+1,表示点的数目。不撞南墙不回头下一句
//【小结】:在树的存储结构上,我们一般选的都是二叉树,因为二叉树可以用静态数组来存储,并且状态转移也很好写(根节点只和左子节点和右子节点有关系)。
//f[i][j]=f[tree[i].lc][j-1]+tree[i].data 多加1个左孩子,少1条边 方案1
//f[i][j]=f[tree[i].rc][j-1]+tree[i].data 多加1个右孩子,少1条边 方案2
//f[i][j]=f[tree[i].lc][k]+f[tree[i].rc][j-2-k]+tree[i].data 多加1个左孩子,1个右孩子,少2条边 方案3
// 1<=k j-2-k>=1 => k<=j-3 故 1<=k<=j-3
//for(i=0;i<=q-1;i++)  f[root][j]=dp(tree[root].lc,i)+dp(tree[root].rc,q-i-1)+tree[root].data;
//i=0 f[root][q]=dp(tree[root].lc,0)+dp(tree[root].rc,q-0-1)+tree[root].data=dp(tree[root].rc,q-1)+tree[root].data; 方案2
//i=q-1 f[root][q]=dp(tree[root].lc,q-1)+dp(tree[root].rc,0)+tree[root].data=dp(tree[root].lc,q-1)+tree[root].data; 方案1
//i=1 f[root][q]==dp(tree[]root].lc,1)+dp(tree[root].rc,q-1-1)+tree[root].data=dp(tree[root].lc,1)+dp(tree[root].rc,q-2)+tree[root].data;
//网上流传比较广的代码有三个地方看不懂:1是只有左孩子,只有右孩子,只有左右孩子,三种情况怎么拼成一个方程
//2是为什么是dp(1,q+1)而不是dp(1,q)
//3是为什么上面的i=1怎么解决?//i=1 f[root][q]==dp(tree[]root].lc,1)+dp(tree[root].rc,q-1-1)+tree[root].data=dp(tree[root].lc,1)+dp(tree[root].rc,q-2)+tree[root].data;
//无奈之下,按自己的理解,动态规划另起炉灶,就用dp(1,q),递归结束条件有3个:1是该值之前计算过;2是还有边,但是已经超出节点范畴,即该分支的节点已经全遍历完了,root=0;3是边已用完,但节点还在,返回该节点的权值。
//上述理解,感觉是能想出的解法,比网上流传的解法,应该更易懂。
//无人指导下,学习新的内容,透彻理解,独立编写,总要两三天时间。 2017-6-11 17:45
//提交codevs上AC,洛谷上,测试点2,3,4WA,翻阅洛谷中的讨论,发现:有的树枝结0个苹果,马上明白了,初始化权值为0不合适,应初始化为-1,太刁钻了。
//《新编全国青少年信息学竞赛培训教材 复赛篇 第2版 》在讲述 二叉苹果树,大体还是讲得不错的,但小错误也不少,个人觉得在三种方案写成一种方案时,讲解不够,并且为什么最后是dp(1,q+1)也未涉及。
//当然,该书也未考虑到, 有的树枝结0个苹果,初步判定测试点2,3,4三个数据,是后来加上的。
#include <stdio.h>
#include <string.h>
#define maxn 100+10
int e[maxn][maxn],n,q,vis[maxn],f[maxn][maxn];
struct node{
    int lc;//左节点
    int rc;//右节点
    int data;//权值
}tree[maxn];
void maketree(int root){//建树
    int i;
    vis[root]=1;
    for(i=1;i<=n;i++)
        if(vis[i]==0&&e[root][i]!=-1){//该节点未使用,root,i之间有边
            if(tree[root].lc==0)
                tree[root].lc=i;
            else
                tree[root].rc=i;
            tree[i].data=e[root][i];//2 此处写成tree[root].data=e[root][i] 1 忘了权值
            maketree(i);//建树用递归
        }
}
int max(int a,int b){
    return a>b?a:b;
}
int dp(int root,int q){
    int i,tmp=0;
    if(f[root][q]!=0)//避免重复计算,记忆化搜索
        return f[root][q];
    if(root==0)//没有节点可用
        return 0;
    if(q==0)//边已用完,只剩节点
        return tree[root].data;
    tmp=max(tmp,dp(tree[root].lc,q-1));//只有左子树
    tmp=max(tmp,dp(tree[root].rc,q-1));//只有右节点
    for(i=0;i<=q-2;i++)//有左右子树
        tmp=max(tmp,dp(tree[root].lc,i)+dp(tree[root].rc,q-2-i));
    tmp+=tree[root].data;//别忘了加上当前节点的权值。
    f[root][q]=tmp;
    return f[root][q];
}
int main(){
    int i,u,v,w;
    memset(e,-1,sizeof(e));//3 memset(e,0,sizeof(e));错了测试点2,3,4 有的树枝结0个苹果
    memset(vis,0,sizeof(vis));
    memset(tree,0,sizeof(tree));
    memset(f,0,sizeof(f));
    scanf("%d%d",&n,&q);
    for(i=1;i<=n-1;i++){
        scanf("%d%d%d",&u,&v,&w);
        e[u][v]=w;//邻接矩阵
        e[v][u]=w;
    }
    maketree(1);
    printf("%d\n",dp(1,q));
    return 0;
}


原创粉丝点击