题目描述

有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)

这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

2 5 \ / 3 4 \ / 1 现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。

输入输出格式

输入格式:

第1行2个数,N和Q(1<=Q<= N,1<N<=100)。

N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。

每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。

每根树枝上的苹果不超过30000个。

输出格式:

一个数,最多能留住的苹果的数量。

输入输出样例

输入样例#1

5 2

1 3 1

1 4 10

2 3 20

3 5 20

输出样例#1

21

算法:

树形DP

 

分析:

这道题其实是要我们计算在一棵二叉树上保留一定数量的枝条情况下的最大权值。

这是一个基础的树形DP模板题,在二叉树上进行动规。我们发现对于树上的任意一棵子树,它的根节点和两个子树存在着关系,我们探究一下,树是由递归性质得到的,所以我们首先要把我们得到的数据先在一个数组上建造出来。

 

把树建好了之后,我们就可以探究它的状态转移方程,我们已知n-1条边,为了方便,我们把它设为双向的边。我设len[x][y]和len[y][x]表示x到y的边上权值为len[x][y](或len[y][x])。

接下来,设num[v]表示以v为子节点的那条边的长度,设f[v][k]为以v为根节点的子树中保留k条边的最大权值,设tr[v][ans]为以v为根节点的左右儿子分别是什么,当ans表示1时,他是左儿子,反之,则为右儿子。

 

把一棵树放到一个线性DP里面想的话,就会容易多了。我要求当前的f[v][k],我首先要求得他的左右儿子分担一共k条边的最大值分别是什么,然后相加即可。

 

可列得状态转移方程为:f[v][k]=max(f[v][k],f[tr[v][1]][i]+f[tr[v][2]][k-i-1]+num[v]);

 

这个东西相信不难看懂,接下来我们需要优化程序。既然树可以递归来建造,那么我们是不是也可以用递归来求解呢?嗯,是可以的。

这是我们就可以将普通的DP放到DFS上做,合成了记忆化搜索。因为有一些枝条我在之前已经计算过了,那么我可以直接拿来用。

 

说几点注意的,最后输出答案要输出q+1的情况,因为在计算过程中我们为了方便,将边的问题都转化成父节点或者是子节点问题,所以需要我们在节点数上再+1。

切记len数组要初始化一个负数,在用完其中一条边时,记得把那条边和另外一条等价的边都给删掉。

 

上代码:

 #include<cstdio>
#include<iostream>
using namespace std; int n,q,tr[][],num[],len[][],f[][]; //数据规模不大,int 型已经足够 inline int read() //读入优化
{
int x=,f=;
char c=getchar();
while (c<||c>)
f=c=='-'?-:,c=getchar();
while (c>=&&c<=)
x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
} void buildtree(int v) //建树
{
int i,ans=;
for (i=;i<=n;i++)
if (len[v][i]>=) //有分叉
{
ans++; //记录并判断左右子树
tr[v][ans]=i;
num[i]=len[v][i]; //记录长度
len[v][i]=len[i][v]=-; //删除两条边
buildtree(i); //递归它的孩子节点
if (ans==) //到了就返回
return;
}
} void dfs(int v,int k) //记忆化搜索
{
if (k==) //边界
f[v][k]=;
else
if (tr[v][]==&&tr[v][]==) //到了叶子节点,其值等于原值
f[v][k]+=num[v];
else
{
f[v][k]=; //先清零
int i,j;
for (i=;i<k;i++) //枚举0到k-1的情况
{
if (!f[tr[v][]][i]) //左儿子记忆化
dfs(tr[v][],i);
if (!f[tr[v][]][k-i-]) //右儿子记忆化
dfs(tr[v][],k-i-);
f[v][k]=max(f[v][k],f[tr[v][]][i]+f[tr[v][]][k-i-]+num[v]); //状态转移
}
}
} int main()
{
int i,j;
n=read();
q=read();
for (i=;i<=n;i++) //初始化
for (j=;j<=n;j++)
len[i][j]=-;
for (i=;i<=n-;i++)
{
int x=read(),y=read();
len[x][y]=len[y][x]=read();
}
buildtree(); //建树
dfs(,q+); //求解
printf("%d",f[][q+]); //注意,切记是q+1
return ;
}

这是一道二叉树的基础DP,我感觉挺好理解的,主要的模型就是递归建树+记忆化搜索DP,简单记为2个DFS。

 

 

嗯,就这样了。

【洛谷P2015】二叉苹果树的更多相关文章

  1. 洛谷 P2015 二叉苹果树 (树上背包)

    洛谷 P2015 二叉苹果树 (树上背包) 一道树形DP,本来因为是二叉,其实不需要用树上背包来干(其实即使是多叉也可以多叉转二叉),但是最近都刷树上背包的题,所以用了树上背包. 首先,定义状态\(d ...

  2. 洛谷p2015二叉苹果树&yzoj1856多叉苹果树题解

    二叉 多叉 有一棵苹果树,如果树枝有分叉,可以是分多叉,分叉数k>=0(就是说儿子的结点数大于等于0)这棵树共有N个结点(叶子点或者树枝分叉点),编号为1~N,树根编号一定是1.我们用一根树枝两 ...

  3. 洛谷 P2015 二叉苹果树(codevs5565) 树形dp入门

    dp这一方面的题我都不是很会,所以来练(xue)习(xi),大概把这题弄懂了. 树形dp就是在原本线性上dp改成了在 '树' 这个数据结构上dp. 一般来说,树形dp利用dfs在回溯时进行更新,使用儿 ...

  4. 洛谷 P2015 二叉苹果树 && caioj1107 树形动态规划(TreeDP)2:二叉苹果树

    这道题一开始是按照caioj上面的方法写的 (1)存储二叉树用结构体,记录左儿子和右儿子 (2)把边上的权值转化到点上,离根远的点上 (3)用记忆化搜索,枚举左右节点分别有多少个点,去递归 这种写法有 ...

  5. 洛谷P2015 二叉苹果树

    题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1. 我们用一根树枝两端连接的结点的编号来 ...

  6. 洛谷 P2015 二叉苹果树

    老规矩,先放题面 题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1. 我们用一根树枝两端 ...

  7. 洛谷—— P2015 二叉苹果树

    https://www.luogu.org/problem/show?pid=2015 题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点 ...

  8. 洛谷P2015 二叉苹果树(树状dp)

    题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1. 我们用一根树枝两端连接的结点的编号来 ...

  9. 洛谷P2015二叉苹果树

    传送门啦 树形 $ dp $ 入门题,学树形 $ dp $ 的话,可以考虑先做这个题. $ f[i][j] $ 表示在 $ i $ 这棵子树中选 $ j $ 个苹果的最大价值. include #in ...

  10. 洛谷 P2015 二叉苹果树 题解

    题面 裸的树上背包: 设f[u][i]表示在以u为子树的树种选择i条边的最大值,则:f[u][i]=max(f[u][i],f[u][i-j-1]+f[v][k]+u到v的边权); #include ...

随机推荐

  1. jQuery之基本选择器

    1. 是什么? - 有特定格式的字符串2. 作用 - 用来查找特定页面元素3. 基本选择器 - #id : id选择器 - element : 元素选择器 - .class : 属性选择器 - * : ...

  2. 定时器应用-点击按钮,div向右移动

    需求是点击button,div就一直往右移动,给个条件left=800px就停止移动,通过定时器来控制. 代码如下: <!DOCTYPE html> <html> <he ...

  3. 2nd scrum站立会议

    scrum站立会议 站立会议是让团队成员每日面对面站立互相交流他们所承担任务的进度.它的一个附带好处是让同组成员了解到工作的情况.本质上是为了团队交流,不是会议报告. 站立会议的目的: 1.让整个团队 ...

  4. freemarker中空值 null的处理 ?exists ?if_exists ?default(“”)

    exists:由空值测试运算符的引入,它被废弃了. exp1?exists 和 exp1??是一样的, ( exp1)?exists 和(exp1)??也是一样的. if_exists:由默认值运算符 ...

  5. 发布vue插件到npm上

    总体分为2个步骤 一,先写好插件 二,发布到npm上面 一,写vue插件 vue有一个开放的方法install,在vue插件需要写在这个方法里面,在vue官网,里面说的很清楚,这个方法里面可以是全局方 ...

  6. GO语言教程(一)Linux( Centos)下Go的安装, 以及HelloWorld

    写在前面: 目前,Go语言已经发布了1.5的版本,已经有不少Go语言相关的书籍和教程了,但是看了一些后,觉得还是应该自己写一套Go语言的教程.给广大学习Go语言的朋友多一种选择.因为,咱写的教程,向来 ...

  7. bootstrap心得

    最近在弄个人的博客,之前对bootstrap的使用老是感觉使用的一般 幸好在看了慕课网的一个老师的实例教程之后,才感觉是真正对前端使用bootstrap有了一点理解 首先就是. 这些标签,其实都是相当 ...

  8. ubuntu 手动apache记录

    1.下载apache tar -xvzf  httpd.xx 解压 2.下载安装pcre Download PCRE from PCRE.org 解压,进入文件夹中 ./configure --pre ...

  9. Luogu4927 梦美与线段树(线段树+概率期望)

    每个节点被经过的概率即为该区间和/总区间和.那么所需要计算的东西就是每个节点的平方和了.修改对于某个节点的影响是使其增加2sum·l·x+l2x2.那么考虑对子树的影响,其中Σl2是定值,修改后Σsu ...

  10. [BJWC2018]Border 的四种求法

    description luogu 给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\). solution 我们考虑转化题面:给定\ ...