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


    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与以往题目是有很大不同的。

  • 相关阅读:
    Linux yum命令重装mysql
    Java多线程编程<一>
    Java内存区域与内存溢出异常
    实现一个线程安全的Queue队列
    Java 原始数据类型转换
    对象-关系映射ORM(Object Relational Mapping)(转)
    2进制,16进制,BCD,ascii,序列化对象相互转换
    Apache MINA 框架之默认session管理类实现
    Struts.properties(转)
    vue常用插件-数字滚动效果vue-count-to
  • 原文地址:https://www.cnblogs.com/Rye-Catcher/p/8478901.html
Copyright © 2020-2023  润新知