基础树形dp

一、树的基本介绍

         树形dp是近几年noip考察的热门,而且这也是dp当中变化极多,综合性极强,对思维能力要求极高的一类模型。这里主要对树形dp做一个简单的入门,为同学们将来面对高难度的树形dp做准备。

         树指的是由n个点n-1条边构成的连通无向图,有的树指定了根节点,称为有根树。树上任意两个点之间有且只有一条简单路径,即不重复经过任何一个点的路径。如果指定根节点,就可以得到整棵树的父子关系。除根节点外,每个节点有且只有一个父亲,但是可以有多个儿子。

二、树上的基本操作

【深搜一棵树】

树实际上是图的一种,所以我们通常使用链式前向星储存并遍历树。如果当前的位置是x节点,那么与x相连的所有节点中,除了x的父亲,剩下的都是x的子节点,下一步就要进入这些子节点递归。下面是深搜一棵树的函数代码

void dfs(int x,int fa)
{
	   ①
    for(int i=head[x];i;i=nxt[i])
    if(to[i]!=fa)
    {
		②
        dfs(to[i],x);
                ③
      }
            ④
}

接下来的各种树的操作,也包括大多数的树形dp都需要以上面的代码为模板来写,所以请务必掌握上面这些代码。

 

【求树上每个节点的深度】

         所谓的深度,实际上就是以根节点为起点的单源最短路。利用树的性质可以在O(n)的复杂度内得到答案,比spfa、dij等图上最短路算法优秀很多。

         根据树的性质,任意两点间有且只有一条简单路径,所以根节点到任意节点的简单路径也只有一条。而这条路径就是根节点到其父亲节点的路径,加上父亲到该节点的路径。所以只需要在②处添加一句dep[to[i]]=dp[x]+1(视情况而定,有可能加的是边权)就可以求出的单源最短路了。

 

【求树上任意两点间的距离】

         既然O(n)可以求出单源最短路,那么分别指定n个根节点,跑n次深度,就可以在n2的时间内得到由任意一个点出发的单源最短路。而且由于简单路径只有一条,最短路就是那个唯一的简单路径,所以任意两点间的最短路就是任意两点间的距离。不需要n3的floyd算法。

【求子树的大小size】

         对于叶子节点,它的子树只有自己一个点,size=1;对于一般的节点而言,size相当于自己每一个子树的大小之和,再加上自己的大小。所以需要在深搜代码的①处加上size[x]=1,在③处加上size[x]+=size[to[i]]。如果需要求出子树的权值和也是一样的操作。

 

【求树的最长链(直径)】

         顾名思义,最长链指的是树上最长的简单路径,虽然这路径可能不止一条,但是路径长度是惟一的。首先从任意一个点出发求深度,找到深度最深的点就是直径的一端(为什么?)。然后从直径的一端出发,再求一次深度,找到第二次深度最深的点就是直径的另一端。这样深度最深的那个点的深度就是树的直径长度。使用这个办法还可以求出直径上具体有哪些点,也可以求出多条直径。

 

三、树形dp入门题

树形dp也和其他类型的dp一样,有三个步骤需要执行:

设计状态:至少有一维表示当前正在考虑的树上节点p

状态转移:一般使用递归(深搜)由p的子节点的dp值得出p的dp 值

边界条件:叶子节点没有儿子,可以只由叶子节点的值得出叶子的dp值

一般来讲,在①处写的是初值,在②处写的是自顶向下的递推内容,在③处写的是自底向上的递推,④处写的是总和、处理等等。

 

【例1 没有上司的舞会】

一个舞会需要邀请人来参加,每个人有一个快乐度,但若邀请了一个人,则不能邀请他的直接上司。每人至多只有一个上司。求舞会能达到的快乐度的最大值。

 

         分析:首先用dp数组的一维表示当前节点,而当前节点来与不来与子节点的情况有直接影响,所以记dp[x][0]为x不来时,以x为根的子树的最大收益,而dp[x][1]表示x要来时,子树的最大收益。如果x不来,那么儿子节点可来可不来;如果x要来,那儿子节点只能不来。dp方程为:

dp[x][0]=∑max{dp[son[i]][0],dp[son[i]][1]}       dp[x][1]=a[x]+∑dp[son[i]][0]

这个dp方程对叶子节点,也就是边界条件也成立,所以不需要特殊照顾边界条件。答案是max{dp[1][0],dp[1][1]},学名叫最大权独立集。

 

【思考】

最小权覆盖集是指这样的东西:任意一条边直接连接的两个点中至少要有一个点被覆盖。覆盖一个点会产生花费,覆盖编号为i 的点的花费是 pi​。最小的花费就是最小权覆盖集。

         它的dp方程是什么?它的答案与最大权独立集的答案有什么关系?两个集合之间又有什么关系?

 

【例2 选课】

N门课程,每门课有一门或没有直接先修课,每门课学习了会给一定学分,现求学M门课能 拿到的最多学分。

 

         分析:实际上这感觉和金明的预算方案类似,也是一种背包问题(体积为1 ,价值为课程的学分)。但是如果拆分物体,会拆出2n之多,显然不可能。如果把先修课和附属课分别建边,就可以用森林来描述这种附属关系了。为了方便,我们让0号节点是最终的根节点,可以把森林化作一棵树。考虑树形背包dp。

         前面提到过,背包dp要有一维表示背包内东西的总体积,而树形dp要有一维表示当前所在树上的节点编号,所以树形背包就是设dp[x][j],x是当前节点,j是背包当前体积。背包dp要枚举所有的j而树形dp要求dp[x]从x的子节点那里得到。把这两个杂交起来,在枚举子节点时同时枚举体积,能够得到这样的子状态,即当前枚举了前i个子节点,而背包体积为j。此时只需要再枚举j中有多少体积分配给了第i个子节点(记为k)就可以。dp方程为dp[x][j]=max{dp[son[i]][k]+dp[x][j-k]}。

         需要注意的一点是,此类树形背包dp枚举k要倒序枚,原因也很简单:这是一种01背包,所以枚举时要保证dp[x][j-k]还没有被第i个物体装入,倒序枚举可以让j-k比j后参与运算,所以对j执行赋值时j-k还没有被赋值。

         这种做法的时间复杂度是nm2,树形背包还可以利用dfs序做到二次方级别的时间复杂度,有兴趣的同学可以自行查阅相关资料并尝试实现。

 

 

posted @   chyuhoin  阅读(101)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示