洛谷P2015 二叉苹果树(树状动归)
发布时间:2021年12月06日 19:13:06
发布人:jqh?
**题目描述**
有一棵苹果树,如果树枝有分叉,一定是分$2$叉(就是说没有只有$1$个儿子的结点)
这棵树共有$N$个结点(叶子点或者树枝分叉点),编号为$1∼N$,树根编号一定是$1$。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有$4$个树枝的树
```livescript
2   5
 \ / 
  3   4
   \ /
    1
```
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
**输入格式:**
第一行$2$个整数,$N$和$Q$分别表示表示树的结点数,和要保留的树枝数量。
接下来$N-1$行,每行$3$个整数,描述一根树枝的信息:前$2$个数是它连接的结点的编号,第$3$个数是这根树枝上苹果的数量。
**输出格式:**
一个数,最多能留住的苹果的数量。
 
**输入样例#1:**
5 2
1 3 1
1 4 10
2 3 20
3 5 20
**输出样例#1: **
21
**说明/提示**
$1\leq Q <N\leq 100$,每根树枝上的苹果 $\leq 3\times 10^4$。
 
 
这道题有个很大的坑。由于所给的数据只表明了两个点相连,并没有说哪个是父节点哪个是子节点。所以我建了一个邻接表,并把每个数据建两个边。这样子节点一定有一条边连接到父节点,具体是第几条边由建表的方式和输入数据的先后决定。在样例数据中,连接到父节点的这条边都是子节点的最后一条边,所以对于二叉树来说,只把前两条边拿出来,并不影响结果。但是其他的测试数据中,连接到父节点的边很可能不是最后一个,这样就会导致结果错误。所以在动归的过程中,要判断由子节点搜到的点是不是父节点,如果是,要跳过这条边,那么下一个就一定是子节点的子节点。
代码:
```cpp
#include <iostream>
#define MAX_N 1000
using namespace std;
struct type
{
    int adjvex,nextarc;
    long int value;
} edge[MAX_N];
int cnt,head[MAX_N],father[MAX_N];
long int dp[MAX_N][MAX_N],value[MAX_N];
//dp[root][num]表示以root为根节点的子树保留num个节点的最大值
void AddEdge(int x,int y,long int v)
{
    ++cnt;
    edge[cnt].nextarc=head[x];//next指向下一条边
    edge[cnt].adjvex=y;//adjvex为当前边的终点
    head[x]=cnt;//当前边被设为顶点x的第一条边
    edge[cnt].value=v;
}
void TDP(int root,int num)
{
    if(num==0)//不保留枝条
        dp[root][num]=0;
    else if(edge[head[root]].nextarc==0)//该节点为叶子节点
        dp[root][num]=value[root];
    else
    {
        //father数组保存子节点的父节点
        //因为该二叉树由无向图得来,子节点可以通过边搜到父节点
        //以下过程用来跳过子节点连接父节点的那条边
        //这条边可能出现在子节点所连接边的任何一个位置,有添加边的顺序决定
        int arc=head[root];//arc为root节点的第一条边
        if(edge[arc].adjvex==father[root])
            //如果arc的终点为root节点的父节点
            arc=edge[arc].nextarc;//arc向后走一个,找到下一条边
        int lchild=edge[arc].adjvex;
        //左孩子结点为arc的终点
        father[lchild]=root;//左孩子结点的父节点设为root
        value[lchild]=edge[arc].value;
        arc=edge[arc].nextarc;//arc再向后走一个,找到下一条边作为root的右孩子节点
        if(edge[arc].adjvex==father[root])
            //同上,防止该边连到root的父节点
            arc=edge[arc].nextarc;
        int rchild=edge[arc].adjvex;
        father[rchild]=root;
        value[rchild]=edge[arc].value;
        for(int i=0; i<num; ++i)
        {
            if(dp[lchild][i]==0)
                TDP(lchild,i);
            if(dp[rchild][num-1-i]==0)
                TDP(rchild,num-1-i);
            dp[root][num]=max(dp[root][num],dp[lchild][i]+dp[rchild][num-1-i]+value[root]);
        }
    }
}
int main()
{
    int N,Q;
    cin>>N>>Q;
    for(int i=0; i<N-1; ++i)
    {
        int x,y;
        long int v;
        cin>>x>>y>>v;
        AddEdge(x,y,v);//x,y不确定哪个是父节点那个是子节点
        AddEdge(y,x,v);
    }
    //由题目知,二叉树根节点一定是1
    //Q+1表示保留Q条树枝,即保留Q+1个顶点
    TDP(1,Q+1);
    cout<<dp[1][Q+1];
    return 0;
}
```
			
热门评论: