首页 > 其他分享 >树形 DP

树形 DP

时间:2024-02-15 11:23:47浏览次数:26  
标签:结点 子树 节点 树形 DP 我们 dp 为根

简单的树上 dp 其实已经在普及组涉及过:自上而下和自下而上传递的性质。
现在我们需要研究更复杂的树上 dp,比如换根 dp 等等。

【树上 dp】

最大子树和

给出一棵带点权的树,求这棵树中的最大权连通块。

因为是无根树,我们人为规定 1 号结点为根。

\(dp[i]\) 表示以 \(i\) 为根的子树内,必须包含 \(i\) 的最大权连通块。

我们发现,“最大权连通块的点权和” 是一个可以自下而上传递的属性。对于每一个结点 \(u\),\(dp[u]\) 初值 \(a[u]\)。
我们先递归处理 \(u\) 的所有子结点。然后对于每一个子结点 \(v\),若 \(dp[v]>0\),则 \(dp[u]\,+\!\!=dp[v]\)。

现在我们求出了 \(dp\) 数组,\(dp[1]\) 就是必须包含 1 的最大权连通块。但是现在还有一个问题,\(dp[1]\) 只是包含 1 的最大权连通块,真正的最大权连通块可能并不包含 1。

对于这个问题,我们的处理是:\(ans\leftarrow \displaystyle \max_{i=1\sim n}(dp[i])\)。

为什么正确呢?因为我们考虑任何一个连通块,无论这棵树以谁为根,它一定有一个深度最浅的点 \(x\),并且只有一个(如果有两个,一个结点有两个父亲)。那么这个连通块就被 \(dp[x]\) 代表了。

战略游戏

给定一颗无根树。如果我们在 \(i\) 上放一个士兵,那么所有与 \(i\) 相连的边都会被瞭望。要使所有边都被瞭望,求最小士兵数。

我们令 1 号结点为根。

\(dp[i][0]\) 表示以 \(i\) 为根的子树中瞭望所有边,\(i\) 不放士兵,所需的最小士兵数。

\(dp[i][1]\) 定义类似,但是 \(i\) 放士兵。

显然所有 \(dp[i][0]\) 初值 0,\(dp[i][1]\) 初值 1。

下考虑一个结点 \(u\) 的 \(dp\) 咋求。

  1. \(dp[u][0]\)。因为 \(u\) 不放,所以与 \(u\) 相连的边都需要靠 \(u\) 的子结点瞭望。所以 \(dp[u][0]=\displaystyle \sum_{fa[v]=u} dp[v][0]\)。

  2. \(dp[u][1]\)。\(u\) 放了,那么子结点就可以考虑放与不放。显然每个子结点的子树之间没有关系。所以 \(dp[u][1]=\displaystyle \sum_{fa[v]=u} \min(dp[v][0],dp[v][1])\)。

选课

这题有点像树上背包,但并不是树上背包。

这题相当于给出了一片 \(n\) 个点的森林。要求选一个点必须选其父节点。现在问你最多选 \(m\) 个点,最大点权和是多少。

我们可以考虑建一个 0 号点,作为每棵树的根的父节点,同时也是根节点,权值设为 0。

定义:

\(dp[i][j]\) 表示在以 \(i\) 为根的子树中,选 \(j\) 个点,并且必须包含 \(i\) 这个点,得到的最大点权和。

初值:

\(dp[i][0]=0,dp[i][x(x\neq 0)]=-\infty\)。

递推:

对于一个结点 \(u\),我们先计算它的子结点的 \(dp\)。然后我们枚举子结点 \(v\)、\(u\) 的子树里面取多少个 \(j\)、\(v\) 的子树里面取多少个 \(k\)。

\(dp[u][j]=\max(dp[u][j],dp[u][j-k]+dp[v][k])\)。(注意:这里 \(j\) 需要从大到小枚举,不然就会出现用当前层更新当前层的情况

可是这样写感觉很奇怪,因为 \(dp[u]\) 里面应该包含了 \(dp[v]\)。但是我们说这没问题,因为 \(dp[u]\) 只包含枚举过的子结点的子树,并不包含现在正在枚举的子树。而每当我们枚举完一个结点 \(v\) 之后,就相当于把 “可以在 \(v\) 的子树中选点”的情况加入了 \(dp[u]\) 了。

注意,这里我们递推完 \(dp[u]\) 了,但是现在的 \(dp[u]\) 是 不包含 \(u\) 的。
所以,我们最后还需要枚举 \(j\leftarrow m\sim 1\),使所有 \(dp[u][j]=dp[u][j-1]+s[u]\).

答案是 \(dp[0][m+1]\),这意味着我们上面枚举的时候要枚举到 \(m+1\)。

【换根 dp】

对于一颗无根树,我们可以考虑固定一个结点为根来求出以这个点为根的答案。但是我们可能需要知道每一个点为根的答案,如果每一个点做根都重新求一遍答案,这就太慢了。
我们可以考虑 “换根 dp”。

具体而言,当我们当前做根的结点是 \(u\),现在尝试以 \(u\) 的子结点 \(v\) 作为根。
这个时候我们发现一件事:对于所有 \(v\) 的子结点,我们在 \(u\) 做根的时候求出的答案依然可以用,唯一变化的是父节点子树。
而父节点子树变化的地方在于:增加了 \(u\) 的子树,但排除 \(v\) 的子树。
如果我们能快速计算这一部分产生的贡献,并将其传递给子结点,我们就可以让子结点快速计算变化的量。

Accumulation Degree

给出一颗无根树,每条边有一个容量,所有叶结点(度为 1)都是汇点。求以哪个点为源点(根)时,从每个汇点流出的水量之和最大。当然汇点不能同时做源点。

先假设 1 是根节点(源点)。

我们令 \(dp[i]\) 表示以 \(i\) 为源点流向 \(i\) 的子树,最大流量是多少。

显然对于每一个点 \(x\),\(dp[x]=\displaystyle \sum_{v\in son[u]}\min(dp[v],c(u,v))\),\(c(u,v)\) 表示 \(u\) 通向 \(v\) 的边的容量。

接下来我们考虑 \(a[i]\):表示以 \(i\) 为根的最大出水量。假设 \(a[u]\) 已经算完,现在求 \(a[v],v\in son[u]\)。

\(a[v]=\) \(v\) 向 \(v\) 的子树的流量 \(+\) \(v\) 向 \(u\) 的子树(\(v\) 的父节点子树)的流量。
而 \(v\) 向 \(v\) 的父节点子树的流量 \(= \min\{c(v,u),\; u\) 向 \(u\) 的父节点子树的流量 \(+\) \(u\) 向 \(u\) 的“所有除了 \(v\) 所在子树的子树” 的流量 \(\}\)。

而 \(v\) 向 \(v\) 的子树的流量 \(=dp[v]\);\(u\) 向 \(u\) 的父节点子树的流量 \(=a[u]-dp[u]\);\(u\) 向 \(u\) 的“所有除了 \(v\) 所在子树的子树” 的流量 \(=dp[u]-\min(c(u,v),dp[v])\)。

所以\(a[v]=dp[v]+\min\{c(v,u),\;(a[u]-dp[u])+(dp[u]-\min(c(u,v),dp[v])\,)\,\}\)。

最后还有一个问题,就是关于叶结点当根的问题。我们可以让 \(dp[\)叶\(]=0\),但是求 \(dp[\)叶的父亲\(]\) 的时候不要和 \(dp[\)叶\(]\) 求 \(\min\),直接返回 \(c(\)叶的父亲\(,\)叶\()\)。


一般换根 dp 都会有两次循环,一次计算以一个点为根的答案,另一次用换根计算每一个点为根的答案。

code

Centroids

给出一棵树,可以删去一条边再补上一条边,请问对于每一个点,是否能通过操作使其变成中心。

显然对于一个点,如果它不是重心,意味着它恰有(不恰有,加起来大于 \(n\))一个规模大于 \(\frac{n}{2}\) 的子树。

显然,我们要从最大的子树中,切下一个规模 \(\leq \frac{n}{2}\) 但尽可能大的子树,然后接到根节点上。
我们关心一颗子树中 一个规模 \(\leq \frac{n}{2}\) 但尽可能大的子树 规模最大是多少,因为这样我们就可以相减,求出切掉之后是否还大于 \(\frac{n}{2}\)。

\(dp[i]\) 表示 \(i\) 向下(\(i\) 下方所有边)切出 \(\leq \frac{n}{2}\) 的最大子树的大小。\(a[i]\) 表示 \(i\) 向上(\(i\) 的父节点子树加上通往父节点的边)切出 \(\leq \frac{n}{2}\) 的最大子树的大小。
考虑如何用 \(a[i]\) 求 \(a[j],j\in son[i]\)。

求 \(dp[i]\) 时,枚举子结点 \(j\)。若 \(sz[j]\leq \frac{n}{2}\),\(dp[i]=\max(dp[i],sz[j])\);否则,\(dp[i]=\max(dp[i],dp[j])\)。

\(a[j]\) 比 \(a[i]\) 多出来的切法是 直接与 \(i\) 相连的边和 \(i\) 所有不是 \(j\) 的子树中包含的边。

\(a[j]=\max\{a[i],\)切与 \(i\) 相连的边的答案\(,\)切 \(i\) 的其他子树的答案\(\}\)。

"切与 \(i\) 相连的边的答案" 分两种,如果是 \(j\) 连向 \(i\) 的边,答案是 \(n-sz[j]\);否则答案是 \(sz[v],v\) 是 \(i\) 的其他子结点。

“切 \(i\) 的其他子树的答案”,相当于把 \(i\) 的除了 \(j\) 的子树的 \(dp\) 都取 \(\max\),这可以用前后缀最大值来做。

还有一个要注意的点:我们要去掉子结点通向父节点的边。

code

【环形 dp】

Naptime G(这个是环形 dp,为后面基环树 dp 做准备)

注:如果只睡一个时间,不增加效用值。如果睡了连续多个时间,从第二个时间开始计算效用值。

我们从简单情况开始,考虑我们在线性怎么做这个问题:

\(dp[i][j][k]\) 表示前 \(i\) 段时间睡 \(j\) 段,第 \(i\) 段时间睡不睡。(1睡 0不睡)

之所以要开第三维 \(k\),是因为第 \(i\) 段睡不睡可能会影响到下一段如果睡的话统不统计效用值,有后效性。

\(dp[i][j][0]=\max(dp[i-1][j][0],dp[i-1][j][1])\),
\(dp[i][j][1]=\max(dp[i-1][j-1][1]+U_i,dp[i-1][j-1][0])\)。

接下来我们考虑环形,因为环形交界处在 \(n\)和 \(1\),不妨讨论第 \(n\) 段睡不睡。

  1. 第 \(n\) 段不睡,正常线性,取 \(dp[n][B][0]\)(第 \(n\) 段必须不睡);

  2. 第 \(n\) 段睡,唯一的影响是如果第 1 段睡就会变成有效睡眠,取 \(dp[n][B][1]\)(第 \(n\) 段必须睡)。

因为 \(n\) 是接在 \(1\) 后面的,在 dp 里我们以 \(0\) 开始递推,所以我们可以把 \(n\) 视作 \(0\)。

这两种情况我们可以做两次 dp,唯一的区别是如果第 \(n\) 段不睡,初值的设定是 \(dp[0][0][0]=0\),其他 \(-\infty\)(不可以在第一段统计贡献);如果第 \(n\) 段睡,初值的设定是 \(dp[0][0][0]=dp[0][0][1]=0\)(可以在第一段统计贡献,是 \(dp[0][0][1]\) 而非 \(dp[0][1][1]\) 是因为我们把 \(n\) 视作 \(0\),但是并没有占用睡觉时间,真正的睡觉时间应该在 \(dp[n][B][1]\) 的时候才计算)。

code

【基环树 dp】

基环树:一棵树,加上一条边(\(n\) 条边的连通图)。可以看成一个环吊着好几棵树。

城市环路

给出一棵基环树,每个点有点权。相邻两点不能同时选。选出若干个点使得点权和最大。

首先,我们可以找出这个环。然后我们枚举环上每一个点,这个点下方吊着一棵树。而在树上这个问题是很简单的,类似 “战略游戏”。

对于环上每个点都做完这个操作之后,我们可以用环上每个点代表这个点吊着的树,可以这么做的原因是环上每个点是否选择,并不影响环上其他点吊着的树。

于是我们就可以做一次环形 dp,第 \(x\) 个数如果选,有 \(dp[x][1]\) 的贡献;如果不选,有 \(dp[x][0]\) 的贡献。

上面的题类似,我们可以讨论最后一个数选还是不选,做两次 dp。

还有一个问题:怎么 \(O(n)\) 找环。我们知道,当我们在深搜的时候如果试图进入一个已访问的结点,那么我们就找到了一个环。我们可以倒着退回去,把环存进链表里面。

code

这里还要注意一个点:可能出现一种只有两个点两条边的环,这种情况我们需要特判。虽然这题没有,但是如果遇到了,建议去掉通向父节点的边。

注:基环树还有另一种处理方法,那就是在环上找一条边删掉,用树的方法处理。

标签:结点,子树,节点,树形,DP,我们,dp,为根
From: https://www.cnblogs.com/FLY-lai/p/18016077

相关文章

  • 区间 DP
    思路:按区间的\(len\)从小到大DP,枚举左端点,算出右端点,再枚举中间的分界点转移有可能是向左右两端各扩展\(1\)步还有时要记录在左/右端点,分开转移把问题划分为区间长度更短的最优子结构注意\(len=1\)时初始化及边界P4342[IOI1998]Polygon这题已经说了去掉一条边后执......
  • 高维前缀和(SOS DP)
    高维前缀和(SOSDP)通常求二维前缀和,用容斥来求但其实,完全可以先做一遍行的前缀和,再做一遍列的前缀和拓展到\(k\)维也是如此,可以在\(O(nk)\)的复杂度求前缀和但怎么和DP扯上关系?可以把第\(i\)维当作阶段,每一维的具体信息是状态先枚举阶段,表示当前固定其它维,只统计这一......
  • TCP和UDP面试题提问
    @目录TCPUDP总结应用TCP(传输控制协议)和UDP(用户数据报协议)是两种计算机网络通信协议,它们在网络通信中起着不同的作用。TCPTCP是面向连接的协议,它在数据传输之前需要在发送端和接收端建立一条连接。TCP提供可靠的数据传输,它使用确认和重传机制来确保数据的可靠性和完整性。T......
  • P1941-DP【绿】
    题目本身只是一道有些难度的普通dp题,题解中有人说可以把这个看作是背包,我不是这么做的便没细看,感觉能把他联想为背包问题的特例的人的发散思维能力真强。不过倒也没必要,常规做即可,用二维数组即可描述状态,dp[i][j]表示只由前i个横向单位长度组成的游戏中以(i,j)结尾游戏所需的最小游......
  • 数位 DP 做题记录
    数位DP数位DP的常见套路就是记录当前到哪一位,是否抵着上界,转移时枚举当前可以填哪些数,做一遍记忆化搜索。P3413SAC#1-萌数题意:求\([l,r]\)中有多少个数中含有回文子串。思路:如果存在回文子串,那么必然有相邻两位相同或者间隔一位相同,在数位DP时额外记录前2位就可以......
  • Codeforces Round 113 (Div. 2)E. Tetrahedron(dp、递推)
    目录题面链接题意题解代码总结题面链接E.Tetrahedron题意从一个顶点出发走过路径长度为n回到出发点的方案总数题解考虑dp\(f[i][0|1|2|3]\):走了i步,现在在j点的方案总数转移:\(f[i][0]=f[i-1][1]+f[i-1][2]+f[i-1][3]\)\(f[i][1]=f[i-1][0]+f[i-1][2]+f[i-1][3]\)\(f......
  • Atcoder Educational DP Contest 题解
    比赛链接:Atcoderr或者题单A:Frog1常规线性dp,注意到对于每个落点来说,青蛙只能由第\(i-1\)个石头或者第\(i-2\)个石头转移得到,那么我们让\(dp[i]\)为当跳到第\(i\)个石头上的最小费用。此时显然有:\[dp_i=\min(dp_{i-1}+\left|h_i-h_{i-1}\right|,dp_{i-2}+\left|h_......
  • 【Java 并发】【队列应用】【二】Tomcat的NioEndPoint中ConcurrentLinkedQueue 的使用
    1 前言这一节我们讲解Tomcat的NioEndPoint中ConcurrentLinkedQueue的使用。2  Tomcat的容器结构本节讲解apache-tomcat-7.0.32-src源码中ConcurrentLinkedQueue的使用。首先介绍Tomcat的容器结构以及NioEndPoint的作用,以便后面能够更加平滑地切入话题,如图11-4所示......
  • POJ--1179 Polygon(区间DP)
    记录22:012024-2-10http://poj.org/problem?id=1179区间DP问题。区间DP问题可能需要注意的点就是是根据区间长度来计算的,随着迭代区间长度不断增加,结果也就计算出来了这种“任意选择一个位置断开,复制形成2倍长度的链”的方法,是解决DP中环形结构的常用手段之一因此读入数......
  • JMeter 进行UDP压力测试
    第一步:安装udp插件第二步:添加线程组,然后按下添加UDP请求设置如下配置你要测试的服务器IP和端口。按照下面的格式输入16进制数数据然后可以开始跑了......