树形DP乱做
时间跨度较大的做题记录,期间更改过码风,但是懒,所以前面代码不改了。
“访问”美术馆
背包问题。容易将美术馆转化成一棵二叉树,将展厅也视为走廊,每个走廊对应二叉树上一个节点。对于这种读入方式可以采用递归建树。
记 \(t_{cost}\) 为通过这个走廊所需的时间,记 \(t_{val}\) 为可以拿到的最多的画,记 \(dp_{i,j}\) 表示在第 \(i\) 个走廊用时 \(j\) 可以拿到的最多的画。
对于每个叶子节点,即展厅,从 \(1 \sim t_{val}\) 枚举拿画需要的时间 \(tim\),因此有
对于非叶子节点,用花费在左右儿子的时间来转移状态。对于当前可用的时间 \(tim\),有
因此可以得到转移方程
最后的结果为 \(dp_{1,n-1}\)。
需要注意的点有:
- 警察到达就算结束,给的 \(n\) 需要减一
- 对于每条走廊走过去还要回来,因此耗费的时间要乘二
[ZJOI2006]三色二叉树
依然采用递归式建树。将最大值和最小值分开考虑,分别存储。由于具有三种颜色,所以对于每个节点存三种状态,分别是颜色为红、绿、蓝时的绿点的最大、小数目。
对于只有一个儿子的节点,这个节点选一种颜色的绿点数量等于儿子的另外两种颜色选绿点数量中的较大值。
对于有两个儿子的节点,这个节点选一种颜色的绿点数量等于儿子的另外两种颜色选绿点数量的和的较大值。
如果是放置了一个新的绿点的话,给答案累加一。
[USACO12FEB]Nearby Cows G
换根DP。记 \(dp_{i,k}\) 表示节点 \(i\) 距离它为 \(k\) 的点的权值和。
对于第一遍 \(dfs\) 统计每个点离它距离为 \(0 \sim k\) 的点权和,父节点由子节点来更改,显然有:
对于第二遍 \(dfs\) 统计答案,记 \(ans_i\) 表示距离节点 \(i\) 不超过 \(k\) 的所有节点权值和。定义两个操作 \(cut(a,b)\) 为将 \(a\) 中 \(b\) 的贡献消除和 \(link(a,b)\) 为将 \(a\) 中加上 \(b\) 的贡献。在到达一个节点的时候直接计算答案,显然有:
在遍历节点的时候进行换根,先抛去父亲节点中要去的这个儿子 \(to\) 的贡献,这样剩下的就是 \(to\) 的兄弟的贡献和,然后再在 \(to\) 的贡献里加上兄弟的贡献,回溯的时候更改回来即可。
[ZJOI2007] 时态同步
最优解来自对于同一深度的节点,将它们的距离统一补齐为其中最深的那个点。
每次处理最深的那一层,统计他们到父节点的距离和 \(sum_i\) 以及记录其中的最大距离 \(dis_i\),然后计算对于这一层来说的答案,再将当前最深层删掉,记录他们的父节点为其中的最大值,也就是 \(dis_i\),一遍 \(dfs\) 就可以计算出答案。
形式化的,记 \(siz_i\) 为 \(i\) 节点儿子个数,可以有:
Computer
换根DP。考虑一个节点距离它最远的点,可能来自它的下方,也可能来自它的上方,即其兄弟节点或后代,容易想到分别维护两个方向上的最大距离,最后取 \(\max\)。
在向上走的时候,如果这个节点所处的是长链,那么向上走的答案便不能来自它本身了,因此还需要维护向下走的次长链,来转移长链中向上走的答案。
记 \(dp_{i,0/1/2}\) 来表示 \(i\) 节点的三个状态。
第一遍 \(dfs\) 用子节点转移父节点向下走的最大值和次大值,遍历整棵树即可。
第二遍 \(dfs\) 用父节点转移子节点向上走的最大值,判一下在不在长链上即可。
重建道路
类似树形背包。
有向树,读入顺便记录儿子数目。记 \(dp_{i,j}\) 为以 \(i\) 号节点为根的子树,大小为 \(j\) 的子树最少需要删掉几条边。边界条件显然有 \(dp_{i,1} = son_i\),即删掉所有与儿子相连的边得到。目标为 \(\min_{1 \le i \le n} dp_{i,m}\)。
考虑用儿子节点转移父亲节点,总共保留 \(j\) 个节点,在一个儿子上保留了 \(k\) 个,则在父亲上要保留 \(j-k\) 个。因为在处理边界的时候我们默认保留了子节点到父节点的边,所以转移的时候要将这个重复的贡献删去,因此有转移方程:
由于最后得到的树需要独立,所以除了以 \(1\) 号节点为根外,其他节点所得出的答案还要再减一,即将与父亲所连得边删除。
[USACO08JAN]Cell Phone Network G
儿子推父亲的套路题。
一个点被覆盖分成三种情况:一定被自己覆盖,一定被父亲覆盖,一定被儿子覆盖。由此得到方程的三个状态,我们用 \(dp_{i,0/1/2}\) 来表示。
如果被自己覆盖,儿子可以被父亲覆盖,也可以被儿子自己覆盖,也可以被儿子的儿子覆盖,所以 \(dp_{i,0} = \min_{son \in i} dp_{son,0/1/2}\)。
如果被父亲覆盖,儿子就只能被儿子自己或者儿子的儿子覆盖,所以 \(dp_{i,1} = \min_{son \in i} dp_{son,0/2}\)。
如果被儿子覆盖,那么他的儿子里面必然有一个被自己覆盖的节点,我们考虑枚举这个节点。如果这个点被儿子覆盖,那么他的儿子只能选择被自己覆盖或者被儿子的儿子覆盖,这个抉择实际上等价于我们上面的当前节点被父亲覆盖。枚举每一个儿子结点的时候,我们先将这个儿子的这一部分贡献从总体中抠出来,再换成必定选择这个儿子节点被他自己覆盖,就可以得到方程 \(dp_{i,2} = \min_{son \in i} dp_{i,1} - \min (dp_{son,0},dp_{son,2}) + dp_{son,0}\)。
初始化被自己覆盖的话初值就是 \(1\),由于被儿子覆盖是求一个最小值的状况,所以初始化成无穷大。
[POI2011] DYN-Dynamite
终于不是纯树形DP题了。
最大值最小化,这个显然是要二分做的,但是二分出来的答案怎么检验呢,考虑树形DP。
我们记 \(dp1_i\) 表示在以 \(i\) 为根的子树中,选取的最近的点离他的距离;记 \(dp2_i\) 表示以 \(i\) 为根的子树中,未被覆盖的最远的关键点离根的距离。对于这两个方程的转移是非常显然的,分别取最大最小即可,初始值设成正负无穷:
然后分类讨论几个情况,首先,特别的,我们将 \(dp2\) 的值设为负无穷的时候,就代表着子树中所有关键点都已经被覆盖,不用再处理了。记当前二分出的答案为 \(ans\),是否为关键点为 \([ok_i=1]\) 可以得到以下几种讨论:
- 当 \(dp1_i + dp2_i <= ans\) 时,说明选取得最近的点也能覆盖最远的关键点,意味着整棵子树都可以被覆盖,就可以将 \(dp2\) 设成负无穷了。
- 当 \(dp2_i = ans\) 时,说明最远的未被覆盖点距离当前节点已经到达上限长度,不选取这个节点就没有覆盖的机会了,所以必须在当前节点覆盖。因此将放置点的个数的计数器累加 \(1\),\(dp1\) 设置成 \(0\) 表示选取当前这个节点,\(dp2\) 设置成负无穷表示都能覆盖。
- 当 \(dp1_i > ans \text{且} ok_i = 1\) 时,说明当前节点是关键点,但是它的子树中选取的点并不能覆盖他,这是就要把这个点丢给他的祖先去处理,未被覆盖的最远的关键点还要考虑它本身,所以 \(dp1_i\) 如果之前有过更远的点就不变,否则就取成 \(0\),即它自己。
[ARC112C] DFS Game
完美的服务 Perfect Service
设 \(dp_{i,0/1/2}\) 分别表示第 \(i\) 个节点自己安装了服务器、自己没有安装但有且仅有一个儿子安装了服务器、自己和儿子都没有安装服务器但父亲装了。
自己装了儿子可以装也可以不装,所以有:
自己和儿子都没有装,父亲装了,那儿子一定是有一个孙子装了,才能保证儿子是合法的,所以有:
自己没装,有且仅有一个儿子装了,相当于是自己和儿子都没有装,减掉一个儿子都没有装,再加上这个儿子装了,所以有:
注意到 \(dp_{i,1}\) 的值受到 \(dp_{i,2}\) 的影响,所以要先一遍算完 \(dp_{i,2}\),然后重新枚举一遍儿子来计算 \(dp_{i,1}\)。
[SDOI2006]保安站岗
设 \(dp_{i,0/1/2}\) 分别表示第 \(i\) 个节点被自己控制、被儿子控制、被父亲控制,显然有初值 \(dp_{i,0} = val_i,dp_{i,1/2} = 0\)。
自己选了那子节点怎么选都是合法的,所以有:
被父亲控制,儿子可以被儿子自己控制,也可以被儿子的儿子控制,所以有:
被儿子控制,儿子可以被儿子自己控制,也可以被儿子的儿子控制,所以有:
但是注意到有可能儿子都选择被儿子的儿子控制,这样没有儿子控制他自己,此时是不合法的,所以应该选择一个代价最小的“被自己控制的儿子”作为这类DP的初值。记这个儿子为 \(x\),假设有一个儿子 \(y\) 比选择 \(x\) 更优,那么根据设计的方程有 \(dp_{x,0} + \sum_{j \in son_i}^{j \neq x} \min dp{j,0/1} > dp_{y,0} + \sum_{j \in son_i}^{j \neq y} \min dp_{j,0/1}\)。移项化简后得到 \(dp_{y,0} - \min dp_{y,0/1} < dp_{x,0} - \min dp_{x,0/1}\),由此可以得到选择的最佳的子节点作为初值,其 \(dp_{i,0} - \min dp_{i,0/1}\) 最小。
[CF280C] Game on Tree
好吧,其实是期望DP。考虑问题转化,将删除操作改成给点打标记,一个操作合法的充要条件是选中的点到根的路径上之前没有被选过的点,要使最后没有可操作的点,答案就是操作序列长度的期望。注意到每个点被选择的概率只由他到根的路径上有几个点来决定,所以最后答案就是 \(\sum_{i=1}^n dep_i\)。
[IOI2005]Riv 河流
不失一般性的,我们可以设 \(f_{i,j,0/1}\) 表示现在在 \(i\) 号节点,我们在 \(i\) 和 \(i\) 的子树中选取了 \(j\) 个关键点,有没有选取 \(i\) 的最小权值和。但是这样我们很快发现了问题——我们并不容易知道具体有多少个节点选择了 \(i\) 为祖先关键点,无法统计答案。既然我们不知道有多少个节点选择了 \(i\) 为祖先关键点,那我们不妨反着考虑:我们能不能很方便的知道当前节点选择了谁作为祖先关键点呢?如果我们知道了这个,那我们只需要把 \(i\) 上的答案再合并到 \(F_i\) 上就好了。
考虑“当前决策对未来的贡献与未来有关”,这个句子和“当前节点对祖先的贡献与祖先有关”简直就是排比句啊!我们可以预测选择的节点是谁,并把它记录在状态中,于是我们就得到了设 \(f_{i,j,k,0/1}\) 表示现在在 \(i\) 号节点,我们在 \(i\) 和 \(i\) 的子树中选取了 \(j\) 个关键点,其中 \(i\) 的祖先关键点为 \(k\),有没有选取 \(i\) 的最小权值和。,首先我们遍历整棵树,在每个节点上先枚举它的祖先关键点是谁,再枚举在它和它的子树中选了几个关键点,对于每个不同数量的关键点的决策做一个类似于树上背包的东西,即对于每一个子节点枚举在它和其子树中共选择了 \(1 \sim k\) 个关键点,综上我们得到了一个时间复杂度为 \(\mathcal O(n^2k^2)\) 的算法。