动态规划-树形动态规划-结点选择

  1. 题目

  • 问题描述

有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?

  • 输入格式

第一行包含一个整数 n 。

接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。

接下来一共 n-1 行,每行描述树上的一条边。

  • 输出格式

输出一个整数,代表选出的点的权值和的最大值。

  • 样例输入

5
1 2 3 4 5

1 2
1 3
2 4
2 5

  • 样例输出

12

  • 样例说明

选择3、4、5号点,权值和为 3+4+5 = 12 。

数据规模与约定

对于20%的数据, n <= 20。

对于50%的数据, n <= 1000。

对于100%的数据, n <= 100000。

权值均为不超过1000的正整数。


  1. 引言:

这是我第一次接触到的树形DP题,一开始完全没有思路。

后面看了看网上大佬的题解,颇有启发。

DP的关键就在于状态的定义以及找转移
首先要考虑清楚状态,状态要能够很好地并且完整地描述子问题

其次考虑最底层的状态,这些状态一般是最简单的情况或者是边界情况

再就是考虑某一个状态能从哪些子状态转移过来,同时还要考虑转移的顺序,确保子问题已经解决

树形DP很多时候就是通过子节点推父亲节点的状态

  1. 分析:

于是我们用一个f[i][k]表示到以i为根的子树中的最佳答案。

同时k=1时表示选这个节点,k=0时表示不选这个节点。

那么,如果用u表示父节点,v表示它的一个子节点时

容易得到

f[u][0]+=max(f[v][0],f[v][1]);

f[u][1]+=f[v][0];

也可以得知:

初始化时,f[i][0]=0;f[i][1]=w[i];

最终状态为max(f[1][0],f[1][1])//要么选根节点要么不选

可是怎么一步步推出此状态呢?

这就要我们先深搜到它每一个叶节点在一步步退回来。

同时节点数比较大,这就要求我们用邻接表存树。

   #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int head[100100];//表头,head[i]代表起点是i的边的编号
    int cnt;//代表边的编号
    int f[1000][1000];
    struct s
    {
        int u;//记录边的起点
        int v;//记录边的终点
        int next;//指向上一条边的编号
    }edge[100010];
    void add(int u,int v)//向所要连接的表中加入边
    {
        edge[cnt].u=u;
        edge[cnt].v=v;
        edge[cnt].next=head[u];
        head[u]=cnt++;
    }
    void dfs(int u,int pre)//pre--父节点  u起点 
    {
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].v;
            if(pre==v)  //若父节点与终点重合(即为叶节点)则不需DP 
                continue;
            dfs(v,u);    //以起点为父亲继续DP 
            f[u][1]+=f[v][0];
            f[u][0]+=max(f[v][0],f[v][1]);
        }
    }
    int main()
    {
        int n;
        cin>>n;
        memset(head,-1,sizeof(head));
        memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++)
            cin>>f[i][1];
        for(int i=1;i<n;i++)
        {
            int a,b;
            cin>>a>>b;
            add(a,b);
            add(b,a);
        }
        dfs(1,-1);//从第一个节点开始
        cout<<max(f[1][1],f[1][0])<<endl;
        return 0;
    }

相信大家也注意到了。

if(pre==v)continue;//若父节点与终点重合(即为叶节点)则不需DP

如果是叶节点的话就不用递推。

  • 总结

这算是最基本的树形DP了,不难理解却也让大家明白了树形DP与以往题目是有很大不同的。

posted @ 2018-02-27 14:53  Rye_Catcher  阅读(947)  评论(0编辑  收藏  举报