ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[复习资料]树形dp-杂题选讲

2021-01-04 21:36:46  阅读:189  来源: 互联网

标签:le sum 选讲 复习资料 times rm 杂题 节点 dp


树形dp,一般指的是在树上的dp,一般情况下,树形dp完全没有重叠子问题,只是单纯地记录一个值罢了,但是我们还是习惯性地称它们为树形dp。

树形dp的状态设置都是很有套路的,在一般情况下我们都把状态设为 \(dp_{u,sta}\) ,表示考虑以 \(u\) 的根为子树,其余状态为 \(sta\) 的答案(或其他),然后转移时一般会忽略掉父节点对其的影响,只考虑自己子树内的点对自己的影响(举个例子:在没有上司的舞会中, \(sta\) 就是设置为该节点是否被选中, \(dp\) 值本身的含义就是选中点最多为多少)。

树形dp在转移时通常只会一棵一棵子树地考虑,每次考虑用儿子的dp值来更新自己的。


先从最基础的模型讲起吧:

树形背包

树形背包和普通背包一样,时间复杂度为 \(\mathcal O(NV)\) ,这里不给出详细证明。

例题:[JSOI2018]潜入行动

给定一棵有 \(n\) 个节点的无向树,问标记恰好 \(k\) 个节点后满足每个节点相邻节点中至少一个被标记(不含自己)的方案数。

\(1\le n\le 10^5,1\le k\le \min(n,100)\)

相邻点被标记以下简称被覆盖。

状态不难设出来,设 \(dp_{i,j,0/1,0/1}\) 表示以 \(i\) 为根的子树中标记了 \(j\) 个点,其中 \(i\) 号节点是否被标记,是否被覆盖的方案数,考虑将子树 \(v\) 并入 \(u\) :

\(u\) 没被标记并且没被覆盖: \(v\) 必须被覆盖,同时 \(v\) 不能被标记:

\[dp_{u,x+y,0,0}\gets dp_{u,x,0,0}\times dp_{v,y,0,1} \]

\(u\) 没被标记但被覆盖了:如果之前 \(u\) 就是没被标记并且被覆盖了,那么这次 \(v\) 可以标记或不标记,但是必须被覆盖;如果之前 \(u\) 没有被标记并且没有被覆盖,那么这次 \(v\) 必须被标记,并且必须被覆盖。

\[dp_{u,x+y,0,1}\gets dp_{u,x,0,1}\times dp_{v,y,0/1,1}+dp_{u,x,0,0}\times dp_{v,y,1,1} \]

\(u\) 被标记了但是没有被覆盖: \(v\) 必须被标记,可以选择被覆盖或不被覆盖。

\[dp_{u,x+y,1,0}\gets dp_{u,x,1,0}\times dp_{v,y,0,0/1} \]

\(u\) 被标记了并且被覆盖了: 如果之前 \(u\) 已被标记且被覆盖,那么这次 \(v\) 可以选择标记或不标记,可以选择覆盖或不被覆盖;如果 \(u\) 之前已被标记且没被覆盖,那么 \(v\) 必须被标记,可以选择覆盖或不被覆盖。

\[dp_{u,x+y,1,1}\gets dp_{u,x,1,1}\times dp_{v,y,0/1,0/1}+dp_{u,x,1,0}\times dp_{v,y,1,0/1} \]

时间复杂度为 \(\mathcal O(nk)\)

总结

如果题目转化后变成类似背包合并这样,那么就可能要用树形背包,一般遇到这种树形背包题,在草稿纸上大力分类讨论一波一般都不会错,所以这种题的套路就是:理清思路,在草稿纸上写下dp转移


换根dp

换根dp属于树形dp的一种特殊的dp,一般情况下,如果题目经过转化后相当于是要求以每个点为根时的答案,那么换根dp就派上用场了。

换根dp之所以快,就是因为它利用了之前求出的答案,如果 \(u,v\) 相邻,那么当根节点从 \(u\) 变成 \(v\) 时,由于树形dp在dp时只考虑了子树,所以只会有两个节点的dp值改变,而其他值都是不变的,同时也正因如此,换根dp的关键就是考虑在根节点改变时考虑对这两个节点dp值的影响,同样的,换根dp的复杂度也取决于一次换根的复杂度。

例题:[APIO2014]连珠线

不会简化题意,直接把原题描述写下来了。

在达芬奇时代,有一个流行的儿童游戏称为连珠线。当然,这个游戏是关于珠子和线的。线是红色或蓝色的,珠子被编号为 \(1\) 到 \(n\)。这个游戏从一个珠子开始,每次会用如下方式添加一个新的珠子:

\(\rm Append(w, v)\) :一个新的珠子 \(w\) 和一个已经添加的珠子 \(v\) 用红线连接起来。

\(\rm Insert(w, u, v)\) :一个新的珠子 \(w\) 插入到用红线连起来的两个珠子 \(u,v\) 之间。具体过程是删去 \(u,v\) 之间红线,分别用蓝线连接 \(u,w\) 和 \(w,v\) 。

每条线都有一个长度。游戏结束后,你的最终得分为蓝线长度之和。

给你连珠线游戏结束后的游戏局面,只告诉了你珠子和链的连接方式以及每条线的长度,没有告诉你每条线分别是什么颜色。

你需要写一个程序来找出最大可能得分。即,在所有以给出的最终局面结束的连珠线游戏中找出那个得分最大的,然后输出最大可能得分。

\(1\le n\le 200000\)

首先转化题意,两个操作可以转化为:

  1. \(\rm Append(w, v)\) :一个新的珠子 \(w\) 和一个已经添加的珠子 \(v\) 用红线连接起来。
  2. \(\rm Insert(w, u, v)\) :一个新的珠子 \(v\) 和一个已经添加的珠子 \(v\) 用蓝线连接起来,同时一个新的珠子 \(w\) 和 \(v\) 用蓝线连接起来。

如果确定了刚开始拥有的点将其作为根,那么题意再次转化:

给定一棵有根树,每次可以选择依次相连的三个深度依次递增的点,并删去连接着这三个点的两条边,执行若干次后,最终被删除的边边权和最大是多少。

考虑dp,设 \(dp_{u,0}\) 表示考虑完以 \(u\) 为根的子树,其中 \(u\) 到它父亲的边不必被删去能获得的最大权值; \(dp_{u,1}\) 表示考虑完以 \(u\) 为根的子树,其中 \(u\) 到它父亲的边必须被删去能获得的最大权值,设 \(w_u\) 表示 \(u\) 和他父亲连边的边权,转移就比较显然了:

\[dp_{u,0}=\sum_{v\ is\ u's\ son}\max(dp_{v,0},dp_{v,1}+w_v) \]

\[dp_{u,1}=dp_{u,0}-\min_{v\ is\ u's\ son}(\max(dp_{v,0},dp_{v,1}+w_v)-(dp_{v,0}+w_v)) \]

方便起见,再设一个 \(dp_{u,2}=\max(dp_{u,0},dp_{u,1}+w_{u})\) ,那么转移:

\[dp_{u,0}=\sum_{v\ is\ u's\ son}dp_{v,2} \]

\[dp_{u,1}=dp_{u,0}-\min_{v\ is\ u's\ son}(dp_{v,2}-(dp_{v,0}+w_v)) \]

\[dp_{u,2}=\max(dp_{u,0},dp_{u,1}+w_u) \]

最后答案就是 \(dp_{root,0}\) 。

这是在根确定的时候我们得到的转移方程,接下来的问题就是换根时如何改变dp值了。

假设当前根是 \(u\) ,然后根要从 \(u\) 换为 \(v\) (其中 \(v\) 是 \(u\) 的儿子)首先改变 \(w\) ,然后更新dp值: \(dp_{u,0}\) 直接减去 \(dp_{v,2}\) 即可,对于处理 \(dp_{u,1}\) 时需要的取 \(\min\) 操作,我们可以记录一个最小值和一个次大值,如果 \(dp_{v,2}-(dp_{v,0}+w_v)\) 是最小值,那么更新 \(dp_{u,1}\) 是就用次小值更新,否则就用最小值,然后更新 \(dp_{u,2}\) ,接着用 \(dp_{u,2}-(dp_{u,0}+w_u)\) 来更新 \(v\) 的最小次小值,最后更新 \(v\) 的dp值即可实现换根。

还有要注意的是,换完根后要换回来。

求出以每个点作为根时的dp值,那么最终答案就是每个点作为根时答案的最大值。

总结

如果题目转化后是要求对于每个点作为根时的答案,就可能是要用换根dp,换根dp首先会求出把某个点作为根时的dp值,然后再考虑换根对dp值的影响


长链剖分

长链剖分优化dp也是一种套路,一般情况下,可以用长链剖分优化的树形dp题第二维状态都是和深度有关的,使用长链剖分可以把 \(\mathcal O(n^2)\) 的复杂度降为 \(\mathcal O(n)\) 。

长链剖分优化实现方法:先找到所有点的重儿子,利用指针 \(\mathcal O(1)\) 继承重儿子信息,其余轻儿子全部扫一遍暴力转移。

为什么长链剖分优化和深度有关的dp可以做到 \(\mathcal O(n)\) ?可以这样考虑:每个点只有在其所在链链顶合并时会对时间复杂度造成 \(\mathcal O(1)\) 的贡献,所以总复杂度就是 \(\mathcal O(n)\) 。

例题:[POI2014]HOT-Hotels 加强版

给出一棵有 \(n\) 个点的树,求有多少组点 \((i,j,k)\) 满足 \(i,j,k\) 两两之间的距离都相等。

\((i,j,k)\) 与 \((i,k,j)\) 算作同一组。

\(1\le n\le 10^5\)

设 \(f_{i,j}\) 表示在以 \(i\) 为根的子树内,离 \(i\) 距离为 \(j\) 的节点个数;设 \(g_{i,j}\) 表示在以 \(i\) 为根的子树内,满足 \(x,y\) 在以 \(i\) 为根的子树内,且若有一个节点 \(z\) 在以 \(i\) 为根的子树外且到 \(i\) 的距离为 \(j\) 时, \(x,y,z\) 两两距离相等的点对 \((x,y)\) 的数目。

考虑一棵子树一棵子树地合并,将 \(v\) 并入 \(u\) :

\[ans\gets ans+f_{u,x}\times g_{v,x+1}+f_{v,x}\times g_{u,x+1} \]

\[g_{u,x+1}\gets g_{u,x+1}+g_{v,x+2}+f_{u,x+1}\times f_{v,x} \]

\[f_{u,x+1}\gets f_{u,x+1}+f_{v,x} \]

不难发现 \(f\) 和 \(g\) 的第二维都是和树的深度有关的,用长链剖分优化,唯一要注意的就是空间要开大还有 \(f\) 和 \(g\) 直接继承时是反着的。

总结

当最终所设状态第二维和深度有关时,可能可以用到长链剖分优化。


重链剖分

既然长链剖分可以优化dp,重链剖分可不可以呢?重链剖分可以用来解决另一类树上问题,就是 \(\rm dsu\ on\ tree\) 。

\(\rm dsu\ on\ tree\) 可以用来解决这一类问题:只有对子树的询问,没有修改,其时间复杂度可以做到 \(\mathcal O(n\log_2 n)\) 。

\(\rm dsu\ on\ tree\) 实现方法:需要两个搜索函数,不妨设为 \(\rm dfs0\) 和 \(\rm dfs1\) ,先找到所有节点的重儿子,然后从根节点开始 \(\rm dfs0\) ,先扫所有轻儿子执行 \(\rm dfs0\) ,回溯时消去所有轻儿子对答案造成的影响,然后再扫重儿子执行 \(\rm dfs0\),回溯时不消去重儿子对答案造成的影响,然后再 \(\rm dfs1\) 一遍遍历到所有除重儿子子树外的点,同时更新答案。

很暴力对不对?但是复杂度是真的。证明方法如下:考虑每个点会被遍历几次,显然 \(\rm dfs0\) 会恰好遍历所有点一次,而如果 \(\rm dfs1\) 遍历到了点 \(u\) ,说明这次 \(\rm dfs1\) 是从 \(u\) 到根的路径上的一条轻边开始的,而重链剖分有个特性:所有点到根的路径经过的轻边数量是 \(\log_2 n\) 级别的,所以复杂度是 \(\rm O(n\log_2n)\) 。

例题:Lomsat gelral

一棵以 \(1\) 为根的 \(n\) 个节点的树,每个节点有一个权值,求每棵子树众数的和。

\(1\le n\le 10^5\)

\(\rm dsu\ on\ tree\) 的模板,算答案需要维护两个值:众数出现次数,众数和。然后直接套模板即可。

总结

如果题目中只有对子树地查询且没有修改,可能要用到 \(\rm dsu\ on\ tree\) ,唯一需要考虑的就是加入或删去一个节点对答案的影响。

其他dp-杂题选讲

例题:一道考试题

考虑一种奇怪的有根树,这种有根树是在普通有根树的基础上,添加了儿子之间的顺序,也就是说我们可以把一个点的所有儿子们看成一个序列,两棵有根树相同,当且仅当它们点数相同,且每个点的儿子序列相同。

现在给定 \(n\) 和数组 \(a_{1\dots n}\) ,你需要计算有几种不同的奇怪有根树,满足对于 \(1\le i\le n\) ,有 \(dep_i\le a_i\) ,其中 \(dep_i\) 表示点 \(i\) 到根节点的距离,也就是有几条边。

\(1\le n\le 100\)

这题dp状态设置和其他题不太一样,也可以算是一种套路,考虑到如果节点 \(u\) 的深度可以为 \(x\) ,那么其深度肯定也可以为 \(x-1\dots 0\) ,于是便考虑从深度大的地方开始放节点。设 \(dp_{i,j,k}\) 表示考虑完深度 \(i\dots n\) ,其中第 \(i\) 层有 \(j\) 个节点,一共用了 \(k\) 个节点的方案数,转移就考虑枚举当前层放几个节点(假设满足 \(a_y\ge x\) 的 \(y\) 有 \(sum_x\) 个):

\[dp_{i,j,k}=\sum_{l=0}^{j-k}dp_{i+1,j-k}k!C_{l+k-1}^{k-1}C_{sum_i-(j-k)}^k \]

例题:[NOIP2018模拟赛] 小P的技能

问深度大于 \(k\) 的 \(n\) 个节点的二叉树有多少个。

\(1\le n\le 500\)

像之前那样按深度dp?显然复杂度是不好的,考虑另一种dp状态设计:设 \(dp_{i,j}\) 表示 \(i\) 个节点深度为 \(j\) 构成二叉树的方案数,转移考虑枚举左子树和右子树的大小和深度:

\[dp_{x+y,\max(a,b)+1}\gets dp_{x+y,\max(a,b)+1}+dp_{x,a}\times dp_{y,b} \]

时间复杂度 \(\mathcal O(n^4)\) ,如何优化?转移也可以写成这样:

\[dp_{i,j}=\sum_{a=0}^{i-1}(\sum_{b=0}^{j-1}dp_{a,j-1}\times dp_{i-a-1,b}+\sum_{b=0}^{j-2}dp_{a,b}\times dp_{i-a-1,j-1}) \]

\[\to=\sum_{a=0}^{i-1}(dp_{a,j-1}\times\sum_{b=0}^{j-1}dp_{i-a-1,b}+dp_{i-a-1,j-1}\times\sum_{b=0}^{j-2}dp_{a,b}) \]

比较显然的前缀和优化,设 \(sum_{i,j}=\sum_{l=0}^jdp_{i,l}\) :

\[\to=\sum_{a=0}^{i-1}(dp_{a,j-1}sum_{i-a-1,j-1}+dp_{i-a-1,j-1}sum_{a,j-2}) \]

时间复杂度: \(\mathcal O(n^3)\) 。

例题:[APIO2016]烟火表演

给定一棵以 \(1\) 为根 \(n\) 个节点的树,每条边有边权,将边权为 \(w\) 的边边权改为 \(w^{'}\) 的花费是 \(\mid w-w^{'}\mid\) ,请用最小的花费使得根节点到所有叶子节点的距离相等。

\(1\le n\le 3\times 10^5\)

设 \(dp_{i,j}\) 表示以 \(i\) 号节点为根的子树到 \(i\) 的父亲的距离全部修改为 \(j\) 所需要的最小花费。

转移:

\[dp_{u,s}=\min_{k=0}^s(\mid val_u-k\mid+\sum_{v\ is\ u'son}dp_{v,s-k}) \]

设 \(g_{u,k}=\sum_{v\ is\ u'son}dp_{v,k}\) ,那么:

\[dp_{u,s}=\min_{k=0}^s(\mid val_u-k\mid +g_{u,s-k}) \]

显然直接扫复杂度太劣了,如何优化?把 \(dp_{u,s}\) 看做是一个自变量为 \(s\) 的函数,观察这个函数的性质。

首先,叶子结点的函数图像一定是斜率为 \(-1\) 的一条折线加上斜率为 \(1\) 的一条折线。

然后从叶子节点手推一下,可以得到以下几个性质:

  1. \(dp_u\) 和 \(g_u\) 为下凸函数。
  2. 由 \(g_u\) 可以直接推得 \(dp_u\) 。
  3. \(dp_u\) 和 \(g_u\) 函数下降段斜率最大为 \(-1\) ,上升段斜率最小为 \(1\) 。

假设函数 \(g_u\) 在 \([L,R]\) 段取值为最小值,由 \(g_u\) 推得 \(dp_u\) 的方法如下:

  1. 将函数在 \([1,L]\) 的折线往上平移 \(val_u\) 个单位。
  2. 将函数在 \([L,R]\) 的折线往右平移 \(val_u\) 个单位。
  3. 将函数在 \([R,+\infty]\) 的折线往右平移 \(val_u\) 个单位并把斜率改为 \(-1\) 。

这时空出了一段斜率为 \(-1\) 的折线,直接连上即可。

还有个性质: \(g_u\) 最右段的斜率是 \(u\) 的儿子数。

但是还有个问题:如何表示这个函数?

我感觉这题最妙的地方就是这里:用转折点的横坐标表示!

我们规定从左往右每经过一个转折点斜率就加 \(1\) ,那么:

  1. 函数相加就是转折点的并(可以在纸上推一下)。
  2. 从 \(g_u\) 转到 \(dp_u\) 相当于是先把横坐标最大的儿子数个的转折点弹出,然后把平行段的 \([L,R]\) 取出,插入 \([L+val_u,R+val_u]\) ,即再弹出两个横坐标最大的两个转折点,把它们加上 \(val_u\) 后再插回去。

那么要实现:

  1. 合并两个点集。
  2. 弹出权值最大的点。
  3. 插入点。

于是,便自然而然地想到可并堆,利用可并堆维护函数图像,最终还原即可得到答案。

标签:le,sum,选讲,复习资料,times,rm,杂题,节点,dp
来源: https://www.cnblogs.com/lsq147/p/14232463.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有