树形 DP

概述

  • 本文因为一些后来的修改,大概率有 typo,如果发现了请提醒我谢谢。

  • 树形 DP 是在树上进行的一类 dp,通常有着树形的决策过程。

    • 欧拉环游序式树形 dp,我们在概述中不讨论。

    • 特别地,真换根 dp 的手法本质上也是欧拉环游序式...不过我们不专门谈这个,在欧拉环游序式外的地方,我们把它当做一个常识来使用。

  • 所谓的“树形决策过程”,一般指的是 dfs 序中的后序遍历(自底向上,较多)、前序遍历(自顶向下,少,多见于假换根)和偶尔得见的 bfs 序。

  • 状态设计一般以子树(我们约定 \(i\) 的子树含 \(i\))为基,即一般以 \(dp_i\) 表示仅考虑 \(i\) 的子树内部时的状态。应当指出的是,这是因为树形结构本身是一种格式塔结构,即任何子树在形式上和全树没有本质区别。

  • 初始化通常为叶节点初始化。也有时所有节点都有与点权(广义,泛指点的信息)相关的初值。

  • 转移。在上面的决策过程我们已经说过了。

    • 一般是后序遍历,形如 \(dp_i=merge(dp_{son})\)。当然 \(dp\) 是要有初值的,可能是在叶节点上,也可能是所有点的 \(dp\) 都要合并其本身的权值。

    • 偶见反过来的前序遍历,典型代表是假换根求 \(dpu(dp_{up})\) 时的 \(dpu_i=dp_{fa}-\text{i's effect in }dp_{fa}\)

    • 当然,其实质是把根定死的变通手段,以达到等效的以 \(i\) 为根的后序遍历效果。

    • 值得一说的是,通常使用逆推实现。这种方式显然是更符合树的图结构的直觉,或者说更舒服的,且有些 DP 的转移关心子树间的关系,难以或不能顺推。

树形背包

  • 典型代表如 \(\text{P1273 有线电视网}\),当然这类问题可以十分复杂,例如 \(\text{P8352 [SDOI/SXOI2022] 小 N 的独立集}\),因其精髓在于复杂度分析。

  • 特点是在全树选给定个点/边/...,求最大/最小/方案数/...的问题。推广地,也包括各种有着和树形背包一样的复杂度分析的 dp(实质为状态大小和子树大小强相关从而可以分析)。

  • 状态通常为 \(dp_{i,j}\) 表示考虑完毕 \(i\) 的子树(含 \(i\)),选了 \(j\) 个的...。有时会需要添加辅助维记录是否选了子树的根。

  • 初始化通常为 \(dp_{i,0}=?,dp_{i,1}=?\)

  • 状态转移方程通常如下:

    • 将 dp 状态展开至三维 \(dp_{i,T,j}\) 表示考虑完了 \(i\)\(T\) 个子树后的 \(dp\) 值,则有:

    • \(dp_{i,T,j}=\max/\min/\sum/..._ {k=0/1}^{j} dp_{i,T-1,j-k}+dp_{son_T,all,k}+val\),其中 \(val\) 是随题目不同而不同的转移系数。

    • 实际实现中往往利用正确转移顺序压掉第二维,以节省空间和减少代码细节。

    • 转移细节很多,一个不慎就会顺序错误或时间假了(错误实现的复杂度大多为上界 \(O(n^3)\),其中 \(n\) 为树的大小)。

    • 对于“方案数”类,往往难以压维,建议开略去第一维的辅助数组辅助转移(这比滚维好写多了)。

  • 时间复杂度大多在 \(O(n^2)\) 级别。

时间复杂度证明

  • 在树上背包的实现过程中,我们必须先令 \(siz=1\),然后初始化,然后每处理完一个子树才合并 \(siz\)

  • 通常我比较喜欢使用顺推的方式,但逆推和它的复杂度是一样的(简证:相当于将数组平移了 \(siz_j\))。

  • 接下来我们证明这样做是对的而先合并 \(siz\) 是错的。

  • 首先我们算一下先合并 \(siz\) 的复杂度。

    • 总复杂度为 \(\sum\limits_{i\notin leaves} \sum\limits_{j\in sons\ of\ i} cost_{i,j}\)

    • 容易推出 \(cost_{i,j}=\sum\limits_{k\leqslant j} siz_k\times siz_j\)

    • \(cost_i=\sum\limits_{1\leqslant k\leqslant j\leqslant T_i} siz_k\times siz_j=\sum\limits_j siz_j^2+\sum\limits_{k<j} siz_k\times siz_j\)。注意这里 \(j,k\) 的定义变了,之前 \(j\) 是正在考虑的子树,现在两者平等,或者把最后一项写成 \((\sum\limits_{k<j} siz_k)\times siz_j\) 会更为直观。

    • 考虑取一个 \(overcost_i=siz_i^2=(\sum\limits_j siz_j)^2\)

    • 容易看出,当 \(i=rt\)\(overcost_i=n^2\),而每一层的 \(\sum overcost=\)\(n\) 拆分成若干份(本层点数份)(各份大小可能相差很大),各份的平方和。

    • 从而 \(\sum overcost\) 不乐观,近似于 \(n^3\),且在树退化成链的时候完全为 \(n^3\) 级别。

    • 下面证明 \(\sum cost\)\(\sum overcost\) 同阶。

    • \(overcost_i=(\sum\limits_j siz_j)^2\) 展开,根据简单的二项式定理(二次方下即为任意对位乘,系数易求),得:

    • \(overcost_i=(\sum\limits_j siz_j)^2=\sum siz_j^2+2\sum\limits_{k<j} siz_k\times siz_j\),其后项占据主导地位。

    • 稍微将上式向上取,得到 \(2\times (\sum\limits_j siz_j^2+\sum\limits_{k<j} siz_k\times siz_j)=2\times cost_i\)

    • 可以看出,我们的向上取没有根本性改变复杂度,而在简单的化归后 \(\sum cost\)\(\sum overcost\) 同阶。

  • 下面我们证明,后合并 \(siz\) 是对的。

    • 此时 \(cost_i=\sum\limits_{k<j} siz_k\times siz_j\) 。嗯?这不是主导部分吗?差异在哪里?

    • 仍然取 \(overcost_i=(\sum\limits_j siz_j)^2\),此时容易看出 \(cost_i=\dfrac{1}{2}(overcost_i-\sum\limits_j siz_j^2)\)

    • 不太显然?进一步展开。\(cost_i=\dfrac{1}{2}(siz_i^2-\sum\limits_j siz_j^2)\)

    • 考虑一下 \(i\)\(son\) 的式子。\(cost_j=\dfrac{1}{2}(siz_j^2-\sum\limits_{j'} siz_{j'}^2)\)

    • 加合!可以看出,\(\sum cost_j\) 的前项和 \(cost_i\) 的后项相消了!

    • 从而我们有着严格的总复杂度 \(\dfrac{1}{2}siz_{rt}^2=O(n^2)\)

P1273 有线电视网

  • 题意:

    • 叶子节点有点权,边有边权。

    • 求在 \(\sum\limits_{lf} v_{lf} - \sum\limits_{e(edge)} v_e \geqslant 0\) 的条件下的 \(cnt_{lf}\) 最大值。

  • 数据范围:\(n\leqslant 3000\)

  • 设计 dp 如下:

    • 状态设计:\(dp_{i,j}\) 表示考虑完毕 \(i\) 的子树(包括 \(i\) 自己)后,在子树中选取了 \(j\) 个叶子节点的最大权值。

    • 初始化:\(dp_{i,0}=0,dp_{lf,1,}=v_{lv}\)

    • 状态转移方程:这里我们把 dp 状态展开成三维的 \(dp_{i,T,j}\) 表示考虑完了第 \(T\) 个子树的结果,则有 \(dp_{i,T,j}=\max_{k=0}^j dp_{i,T-1,j-k}+dp_{son_T,k}+e.l\)。实际实现中,选择使用正确转移顺序压掉第二维。

  • 得解,时间复杂度 \(O(n^2)\)

P3177 [HAOI2015] 树上染色

  • 题意:在一棵 \(n\) 个点的带权边树上选 \(k\) 个点染成黑色,其余点都是白色。求最大的 \(\sum dis(i,j)(col_i=col_j)\)

  • 数据范围:\(n\leqslant 2\times 10^3\)

  • 考虑设计 dp。

  • 注意到距离本身是不可求的,考虑转化题意。

  • \(\sum dis(i,j)(col_i=col_j)=\sum\limits_{e\in edge} e.l\times t_e\),其中 \(t_e\) 为边 \(e\) 被经过的次数。

  • 考虑怎么计算这个次数。

    • 当一个子树内的点色确定之后,我们就一定知道子树内/外的黑白点数量分别是多少。

    • 从而在这个子树的父亲处我们可以方便地计算到这个子树的边的经过次数:\(t_e=num_{in,black}\times num_{out=k-in,black}\)

    • 这里只给出了黑色点的,白色点类似,略去。

  • 于是设计 dp 如下:

    • 状态设计:\(dp_{i,j}\) 为考虑完 \(i\) 的子树染了 \(j\) 个黑色点的总价值(只考虑子树内)。

    • 初始化:\(dp_{i,0}=dp_{i,1}=0\),其他都不可行,即极小值。

    • 状态转移方程:\(dp_{i,T,j}=\max_{k=0}^{j} dp_{i,T-1,j-k}+dp_{son_T,k}+t_e\times e.l\)

  • \(O(n^2)\),得解。

T4 砍树(后半部分)

  • 参见“T4 砍树”(解题报告)。

P8564 ρars/ey

  • 题意略。没什么特色...

  • 好像真的没什么说的。说一句吧:\(dp_{i,j}\) 表示删了 \(j\) 个的状态设计比较舒服。结束。

  • 另外吐槽一下例 2 这...我现在不想重构它,所以先这样吧。

P8352 [SDOI/SXOI2022] 小 N 的独立集

  • 参看自动机上 DP-DP 套 DP。

P4099 [HEOI2013] SAO

  • 题意:给定一张 \(n\) 点,\(n-1\) 条有向边的图,保证其弱连通(换言之,弱连通意义下构成一棵树),求拓扑序方案数。

  • 数据范围:\(T\leqslant 5,n\leqslant 10^3\)

  • 很妙的一道题...但其实也比较自然?上手之后我对着“拓扑序”推了挺久,结果吗...显然是创死了,毕竟从那个方向推出来的东西大多都不依赖树,这显然是不可能放你过的。倒不如说我啥都没推出来,只是又强化了“一般图拓扑序计数不可做”的记忆...

  • 转而考虑用树形 DP 的骨架。不妨定义 \(dp_i\)\(i\) 子树内的方案数,定义 \(fr\) 为指向 \(i\) 的儿子,\(to\)\(i\) 指向的儿子,从而可以进一步定义 \(k\) 阶的 \(fr,to\)

  • 开始设法拼拓扑序。简单起见,先考虑 \(fr\),后考虑 \(to\)。一开始 \(i\) 处的拓扑序是只有 \(i\) 自己的单元素序列,第一个 \(fr\)...显然 \(fr^k\) 构成的树形结构都要扔到 \(i\) 前面,\(fr\) 子树内的东西可以插入到 \(i\) 前的那一个空及 \(i\) 后面的所有空里。然后...

  • 哦豁。问题来了。这种合并不具备...怎么说呢,就是合并完一次后,\(i\) 在拓扑序中的位置不可知了,准确地说其在 \(dp_{i,1}\)(转移完前一个子树)对应的不同方案中大概率有着不同的位置,每个位置的方案还不一定一样。换言之,只有第一次合并是可行的...

  • 不过我们还有很多操作空间:上面的那个 dp 怎么看都是 \(O(n)\) 的。所以考虑升维...注意到,我们在合并中需要知道 \(i\) 在哪里,从而将 \(fr\) 放置到 \(i\) 前/将 \(to\) 放置到 \(i\) 后,然后...额,然后剩下的点怎么办?挨个枚举然后插空?那这个复杂度岂不是...

  • 别急...首先要知道 \(i\) 在哪里,所以把 dp 转定义成 \(dp_{i,j}\) 表示 \(i\) 的子树内,\(i\) 的拓扑序为 \(j\) 的拓扑序方案数。那么总复杂度为...哦这样转移的话要枚举 \(fr/to\) 对应子树内每个点、\(i\) 到对应点这条链的拓扑序,显然炸了。

  • 啧。太愚蠢了...和所谓的“背包合并必死”不同,我们在这里恰恰需要一点拓扑序合并之类的东西(不如说这才是这个 dp 的本质),更准确地说,容易发现上面那个转移根本不是转移,就是个暴力搜。注意到我们先前已经知道了 \(fr/to\) 在对应子树内的拓扑序为 \(k\) 的方案,于是可以暴力枚举 \(fr/to\) 原先的拓扑序,现在插到哪里,然后前后的拓扑序进行合并。

  • 完蛋好像上面这个 dp 不知道转移到哪里去...那还得加一个枚举,枚举 \(i\) 的新拓扑序(注意这里是个树上背包),于是复杂度好像为 \(cost_i=\sum\limits_j siz_j^2 (\sum\limits_{k<j} siz_k)^2\),我分析不能,但怎么看都炸得没边吧?!

  • 写一下发现枚举插到哪里毫无意义,于是变成 \(cost_i=\sum\limits_j siz_j^2 \sum\limits_{k<j} siz_k\),显然还是不太行,考虑优化。先把转移式写出来,因为下标太多所以记子树根为 \(u\),儿子为 \(v\),这里以 \(u\to v\) 的情况为例:

\[dp_{u,i}'=\sum\limits_{j=1}^{siz_u} dp_{u,j}\times \sum\limits_{k=1}^{siz_v} dp_{v,k}\times \binom{i-1}{j-1}\times \binom{siz_u+siz_v-i}{siz_u-j} \]

  • 发现所有转移系数都和 \(k\) 无关,于是对 \(dp_{v,k}\) 做后缀和(这里有着 \(i-j<k\) 的限制条件哦),复杂度下降到 \(cost_i=\sum\limits_{k\leqslant j} siz_k\times siz_j\),典型的树上背包复杂度,\(O(n^2)\)。因为关键的 \(siz_j\) 部分被优化了,想要保证复杂度正确需要在实现上注意一些细节。

  • 我的评价是:这道题跟鬼一样,做完我感觉我没学过背包、组合数学和拓扑序。

其他树上依赖式问题

  • 典型代表如最大权树上独立集/最小权树上点覆盖。

  • 特点类似状压 DP 中的依赖式问题——事实上,它们也许只是上树了。

  • 其中最典型的背包问题,我们已经在上面讨论过,现在我们要讨论一些...百花齐放的问题了。但大体来讲,还是有一些共性:

  • 通常给出一棵 \(n\) 个点的树,点可以有点权,点之间有一些依赖关系,求最大总价值/方案数。

    • 所谓依赖,可能是物品之间互相依赖对方存在或不存在。
  • 状态设计一般为 \(dp_{i,\dots}\) 表示考虑完 \(i\) 的子树,并添加数量不定的辅助维来表示对某些条件的满足情况。

  • 初始化一般为 \(dp_{lf,\dots}=?\)\(dp_{i,\dots}=?\),取决于所设计的状态的起始是 leafy 的还是 nodey 的。

  • 状态转移一般为 \(dp_{i,\dots}=merge(dp_j,i)\)。这里的 \(dp_j\) 是对所有儿子的统称,后面我们默认 \(j\in sons_i\)

  • 复杂度视辅助维而定。

P1352 没有上司的舞会

  • 题意:求树上最大权独立集。

  • 考虑设计 dp 如下:

    • 状态设计:\(dp_{i,0/1}\) 表示考虑了 \(i\) 的子树,不选/选 \(i\) 本身的情况下的最大总价值。

      • 既然只考虑了 \(i\) 的子树,这里的最大总价值也就是从 \(i\) 的子树中的点来的。
    • 初始化:\(dp_{lf,0}=0,dp_{lf,1}=w_{lf}\)

    • 状态转移方程:\(\begin{cases} dp_{i,0}=\sum \max(dp_{j,0},dp_{j,1}) \\ dp_{i,1}=\sum dp_{j,0} \end{cases}\)

  • 最小权点覆盖可以用类似的方式求出。

  • 本题可以推广到动态情况,即 \(\text{P4719 [模板]动态 DP/动态树分治}\)。参看结合律优化。

P8867 [NOIP2022] 建造军营

  • 题意:

    • 给定一张 \(n\) 个点的无向连通图,可以在点和边上驻守。

    • 求合法的驻守方案数。满足以下条件的驻守方案被称为合法的驻守方案:

      • 驻守了至少 \(1\) 个点。

      • 不存在一条边 \((u,v)\),其没有被驻守,且将其删除后至少有一对驻守点不连通。

  • 数据范围:\(n\leqslant 5\times 10^5,m\leqslant 10^6\),保证图连通,无重边,无自环。

  • 首先容易看出我们要先建边双重构树,于是问题变成树上选点,选的点之间的边必须驻守,其他边(包括树边和边双内部的边)都爱守不守。

  • 于是考虑设计如下的 dp 状态:

    • 状态设计:\(dp_{i,0/1/2}\) 表示考虑完 \(i\) 的子树,不选点/选了点但不打算在 \(i\) 的子树外选点/选了点且还打算在 \(i\) 的子树外选点的方案数。

      • 后两者的区别在于(记 \(lca\)\(i\) 的子树内选的所有点的 \(lca\)):

      • \(1\)\(p_{i,lca}\) 上的边爱选不选,这里 \(p\)(path)表示树上两点路径。

      • \(2\) 中这些边必须选。

    • 初始化:

      • 容易看出 \(dp_0\) 不需要 dp,即若将边双内的边带来的系数摘出去,则有 \(dp_{i,0}=2^{siz_i-1}\),这里 \(siz_i\)\(i\) 的子树大小(认为每个边双自身的大小为 \(1\),不考虑所含点数影响)。

      • \(dp_{lf,1}=dp_{lf,2}=\sum\limits_{k=1}^{vsiz_{lf}} C(vsiz_{lf},k)\)。这里 \(vsiz_i\) 表示边双 \(i\) 对应的原图上点的数量。

    • 状态转移方程:

      • 为了避免算重我们得花点功夫。\(dp_{i,0}\) 不谈,对另外两者分类讨论,用一点辅助 DP:

      • \(dp_{i,1}\):两个来源。这里认为自己也是“一个儿子方向的子树”:

        • 只有 \(1\) 个方向的儿子的子树有点:
      • 实质上是只选自己或只选某个方向的子树。前者不论,后者代表着转移来源是 \(dp_{j,1}\)

        • 状态:\(tp_{k,0/1}\) 表示考虑完前 \(k\) 个儿子,是否已经选了某个方向的儿子的子树有点的方案数。

        • 初始化:\(tp_{0,0}=1,tp_{0,1}=\sum\limits_{k=1}^{vsiz_{i}} C(vsiz_{i},k)\)

        • 转移:\(\begin{cases} tp_{k,1}=tp_{k-1,1}\times 2dp_{j,0}+tp_{k-1,0}\times 2dp_{j,1} \\ tp_{k,0}=tp_{k-1,0}\times 2dp_{j,0} \end{cases}\)

        • 有至少 \(2\) 个方向的儿子的子树有点:

          • 实质上是各方向子树“汇聚”在这里,故转移来源为 \(dp_{j,2}\)

          • 状态:\(tp_{k,0/1/2}\) 表示考虑完前 \(k\) 个儿子,选了 \(0/1/\geqslant 2\) 个方向的儿子的子树有点的方案数。

          • 初始化:\(tp_{0,0}=1,tp_{0,1}=\sum\limits_{k=1}^{vsiz_{i}} C(vsiz_{i},k),tp_{0,2}=0\)

          • 转移:\(\begin{cases} tp_{k,2}=tp_{k-1,2}\times (2dp_{j,0}+dp_{j,2})+tp_{k-1,1}\times dp_{j,2} \\ tp_{k,1}=tp_{k-1,1}\times 2dp_{j,0}+tp_{k-1,0}\times dp_{j,2} \\ tp_{k,0}=tp_{k,0}\times 2dp_{j,0}\end{cases}\)

        • 于是我们有 \(dp_{i,1}=tp_{sons,1}+tp_{sons,2}\)(分别是两种 \(tp\))。

        • \(dp_{i,2}\):相对简单,一个来源。约定同上:

          • 状态:\(tp_{k,0/1}\) 表示考虑了前 \(k\) 个儿子,是否已经选了某个儿子方向的子树的方案数。

          • 初始化:\(tp_{0,0}=1,tp_{0,1}=\sum\limits_{k=1}^{vsiz_{i}} C(vsiz_{i},k)\)

          • 转移:\(\begin{cases} tp_{k,1}=tp_{k-1,1}\times(2dp_{j,0}+dp_{j,2})+tp_{k-1,0}\times dp_{j,2} \\ tp_{k,0}=tp_{k-1,0}\times 2dp_{j,0}\end{cases}\)

          • 于是有 \(dp_{i,2}=tp_{sons,1}\)

      • 容易看出,主要是在避免完全不选(\(0/1\) 之分)和确保汇聚有意义(\(1/2\) 之分)。前者做不到会有空的情况,后者做不到会导致把只有一个方向的儿子的子树选了的情况算重。

  • 总复杂度 \(O(n)\),常数稍大,细节一车。

P5689 [CSP-S2019 江西] 多叉堆

  • 题意略。

  • 首先我们考虑设计一个朴素 dp:

    • 状态设计:\(dp_i\) 表示以 \(i\) 为根的子树(其子树大小是固定的)的多叉堆方案数。

    • 初始化:\(dp_{lf}=1\)

    • 状态转移方程:不妨记 \(S\) 为已经考虑过的点数和,注意开始时 \(S=1\) 因为要考虑子树的根,于是 \(dp_i=\prod\limits_j \binom{siz_i-S}{siz_j}dp_j\)

  • 唔,我们注意到只要预处理阶乘就是可做的。现在设法将其推广到并查集模式,发现只要在连边时 \(dp_i\times \binom{siz_i-1}{siz_j}dp_j\) 就可以很方便地维护了。\(O(n+Q)\)

  • 和教练讨论了一下,本题想要推广到一般的树间连边方式是必须 LCT 的,因为必须 DDP,然而没有办法做一个固定的重剖或者全局平衡二叉树(这个东西虽然我不会,但显然是静态的)下来。至于 dp 满不满足可减性,看式子是满足的,所以也允许断边。具体怎么做和时间复杂度?我当然都不知道啦,我又不会 LCT,更别提 LCT 上 DDP 了。

[AGC034E] Complete Compress

  • 题意略。

  • 首先这么小的数据范围暴力枚举根大概率是可行的,故我们考虑定根情况。容易发现一条祖孙链上的点进行聚集是无意义的,考虑设计树形 dp,大概是这样:

    • 状态设计:\(dw_i/up_i\) 分别表示将 \(i\) 子树内的所有棋子移到 \(i\) 的欠账下界和上界。所谓欠账,就是有多少次是虚空配对。

    • 初始化:这个不太传统...认为它没有吧。

    • 状态转移:显然有 \(up_i=\sum\limits_j up_j+sumw_j\),这里 \(sumw\) 是子树内棋子数量。至于 \(dw\)...考虑一个贪心的配对:没有主元素则一定只考虑总需求次数的奇偶性,否则考虑主元素的残量。

    • 为什么这是对的呢?唔,不是很好证,但容易找到一些霍尔定理和摩尔投票的影子,当然,不能直接套用...大体上基于“将最大的两个消去 \(1\) 不劣”。但严谨证明的话...我不是很会,也不是很想想。

  • 然后处理一下细节就结束了,\(O(n^2)\)。当然也是可以换根的...但显然较为复杂,简单起见,直接将相关的两个点的 \(dw\)\(up\) 技巧性地重构(直接暴力重构就被菊花干废了),\(up\) 显然是 naive 的,对于 \(dw\) 考虑花 \(O(n)\) 的空间把合并时的排序结果记录,然后应该可以做到全局 \(O(n)\)。事实上我个人不太喜欢真换根,所以...我偷个懒,不考虑具体的了。反正后面还有要换的...

CF1626E Black and White Tree

  • 题意:对树上每个点作为出发点,求是否能变换到一个黑点上。变换为选一个黑点向它(沿简单路径)走一步,相邻两次变换的选取对象不能相同。

  • 数据范围:\(n\leqslant 3\times 10^5\)。换根吧您!

  • 啊啊...还是先考虑定根情况(啊这个真的算“根”吗...),一个点可行,要么它本身是黑的,要么它...啧这样根本不规约化啊,考虑设计一个 dp 来刻画它。

    • 状态设计:\(dp_i\) 表示从 \(i\) 出发,走到 \(i\) 子树内某个黑点是否可行。显然这一状态不失一般性,因为反复在不同子树间拉扯是没道理的。

    • 初始化:\(dp_i=c_i\)。这里 \(c_i=1\) 表示 \(i\) 为黑色。

    • 状态转移:\(dp_i=[c_j=1]\text{ or }[dp_j=1 \And csum_j>1]\)

  • 唔唔,考虑怎么换根。技巧性地重构。啊,想到办法了,把 \(dp\) 的设计从 \(01\) 改成有多少个方向的子树可行就是多少。额...再记一下每个点对父节点的贡献。解决了,\(O(n)\),这个换根还不算太烦。

  • upd:写完之后发现确实不算很烦...鉴于“喜欢和不喜欢某种算法(实践意义上)都是一种病”,也许我该觉得“哎呀,那没什么(杰哥语气)”?

CF1498F Christmas Game

  • 题意:树上阶梯博弈,步长为 \(K\),舍而不入,对所有根询问胜负情况。

  • 数据范围:\(n\leqslant 10^5,K\leqslant 20\)

  • 老规矩...定根。首先把阶梯的奇偶性求出来,抛掉偶层的石子,剩下的求异或和...应当指出,同深不同点的是不同阶梯。

  • 然后...然后乐了啊,这个换根,啧。本质上是子树内深度模 \(2K\) 为某个值的所有点参与/不参与异或和的区别。

  • 考虑记录 \(xsum_{i,j}\) 表示以 \(i\) 为根的子树内(注意,该数组总是相对现在的实际根而存在的),深度在模 \(2K\) 意义下为 \(j\)(相对于 \(i\))的所有点的异或和。

  • 那么换根的时候来一波令人窒息的操作,把要加的加,要减的减,就完了。\(O(nK)\)

  • 但是!我的手有它自己的想法!既然取模了,直接在求答案的时候硬算一遍,于是只需要对 \(xsum\) 换根,实现简单很多!

P8820 [CSP-S 2022] 数据传输

  • 题意略。

  • 注意到 \(K\leqslant 3\),考虑从这里入手来做。容易看出当 \(K=1/2\) 时,不可能走出链,当 \(K=3\) 时只会访问到和链直接相邻的链外点,且走到它和从它出发的两次移动的步长必然都为 \(3\),否则一定不优,理由显然。

  • 先考虑单组询问。很容易想到 dp 来实现它,考虑设计一个不关心 \(k\) 的 dp:

    • 状态设计:\(dp_{i,j}\) 表示当前走到 \(i\) 了,当前这步还剩至多 \(j\) 长度没有走的最小总代价。

    • 初始化:\(dp_{s,K}=c_s\)

    • 状态转移方程:\(dp_{i,j}=dp_{i-1,j+1}\),然后 \(dp_{i,K}=\min(\min(dp_{i,j})+c_i),dp_{i,K-1}=\min(dp_{i,K-1},\min(dp_{i,j(j>=2)})+mnc_i)\),这里 \(i-1\)\(i\) 在路径上的前驱,转移顺序...按上面的顺序来就是对的,\(mnc_i\) 是和 \(i\) 相邻的所有点 \(j\)\(c\) 的最小值。

  • 好的...显然我们首先可以重剖,构造 \(a=4\) 的转移矩阵,将 \(lca\) 处的两个(显然拐弯的情况更强)dp 矩阵都求出来,然后 \(dpa_x\)\(dpb_{K-x}\) 对位合并,因为还剩 \(x\) 步的言下之意就是用了 \(K-x\) 步。这么做的复杂度为...不妨预处理重链的整体转移矩阵,则为 \(O(n\log n\times a^3)\),纸面是能过的。

  • 但本题不带修,所以我们也可以整静态做法。倍增预处理转移矩阵,复杂度也为 \(O(n\log n\times a^3)\)。我们采用这种实现。

P5021 [NOIP2018 提高组] 赛道修建

  • 题意略。

  • 首先容易想到树形背包的做法。虽然看了数据范围之后我们知道这是扯淡,但是还是谈一下,因为我觉得也算有启发性:

    • 状态设计:\(dp_{i,j}=(k,l)\) 表示考虑完 \(i\) 的子树,当前有 \(j\) 条道路,已经完结的所有道路中最短的长度为 \(k\),手上这条的长度为 \(l\)

    • 啊,假了。一个经典的问题:这个二元组是不可估价的。我们不知道 \(k\)\(l\) 哪个更重要。维护左下凸壳...乍一听有道理,然而光状态数就炸飞,何况这合并还是得暴力合吧。

  • 意识到得设法去掉一个要素,且看到背包不可行,自然地想到二分答案。转而二分道路长度下界,设计如下 dp:

    • 状态设计:\(dp_{i,j}\) 表示考虑完 \(i\) 的子树,当前有 \(j\) 条足够长的完结道路的条件下,手上这条道路的长度最长是多少。

    • 果然!乐,我果然下意识设计了一个背包状态。然而复杂度不给过。正确的设计是...

    • 状态设计:\(dp_i=(num,len)\) 表示考虑完 \(i\) 的子树,当前最多有 \(num\) 条足够长的完结道路,此条件下手上这条的长度最长为 \(len\)。因为这时完结道路数更多是严格优的!

    • 初始化:\(dp_i=(0,0)\)

    • 状态转移方程:记当前二分的答案为 \(temp\),则...哎呀这个不太好代数化,简单来说就是道路数是求和然后贪心配对,道路长度是剩下的未完结道路中长度最大的那个的长度。注意这里的道路长度要加上对应的 \(e.l\)。注意足够大的道路可以不配对,自成一个完结道路。注意贪的时候要枚举小的,找足够大的,因为我们要设法把大的留下来。注意如果找到的大的自成一个完整道路那么不应该配对。

  • 如果配对部分使用 multiset 或 ta 之类的,则复杂度为 \(O(n\log^2)\),足够通过本题。

欧拉序式问题

  • 欧拉序式问题大体上可以分为两种,即欧拉环游序式和欧拉序式。我的叫法可能不太对,总之就是深度优先地遍历整棵树,若退出点则退栈则为欧拉环游序式,否则为欧拉序式。

  • 典型代表分别为 P2414 [NOI2011] 阿狸的打字机 和 天天爱跑步。

  • 先谈前者。前者的特点在于,dp 的内容是从根到这里的一条链相关的信息,于是利用欧拉环游序实现贡献的计入和舍去(必须一次算完,故是离线的),达成 \(O(n)\) 算出所有点的 dp 值的效果。

  • 然后是后者。后者的特点在于,dp 的内容是子树性的,但转移量无法接受,此时利用信息的差分性(当然,得满足才行),维护已经遍历过的所有点的信息,于是在某个点入栈时记录一下,出栈时进行差分(实际上是时序上的差分,故也是离线的),达成 \(O(n)\) 算出所有点的 dp 值的效果。

P3478 [POI2008] STA-Station

  • 题意:求使 \(\sum\limits_i dep_i\) 最大的 \(rt\)。我们把本题当做换根 dp 的板子来谈。

  • 首先容易随便选一个根算出对应的结果,然后开始换根,方便起见我们按 dfs 序换根,每次是两个子树的 \(dep\) 会变化。具体地,从 \(i\)\(j\)\(res'=res+N-2siz_j\);反过来,也即退栈时(因此认为它是欧拉环游序式),\(res'=res-2siz_j+N\)

  • 当然也可以可持久化,但一般需要换根的题目的 \(n\) 都很大,而会改变的东西未必只有这么一点,可能要使用主席树之类的,先不谈是否平添 \(\log\),这么麻烦,意义何在...故不如套欧拉环游序式的壳子来做。

P2414 [NOI2011] 阿狸的打字机

  • 题意略。

  • 显然首先建出 AC 自动机,然后很容易做如下题意转换:

  • 求一些 trie 上的链(其实是文本串)和一些 fail 树上的子树(其实子树的根是模式串的结尾点)的交集大小。

  • 有一个显然的线段树合并做法,即对链的每个点 ins 操作,对 fail 树的节点线合,\(O(|AC|\log)\)

  • 正解妙妙做法:

    • 先把整棵 fail 树拍成 dfn 序,方便我们后面求子树和。

    • 使用欧拉环游序式 dp,在时分别枚举所有对该链的询问,用树状数组或者线段树乱搞一下求子树和然后差分即可。

    • \(O(|AC|+q\log n)\),其中 \(n\) 为树的大小。

P1600 [NOIP2016 提高组] 天天爱跑步

  • 题意略。

  • 将每个人的路径拆成上下行两段,设计如下 dp:

    • 状态设计:\(f_{i,j}\) 表示在第 \(j\) 秒时经过 \(i\) 的人数。鉴于上下链的转移方向不一样,我们肯定是要做两次。

    • 初始化:即起始点的影响。

    • 状态转移方程:\(f_{i,j}=\sum\limits_{k\in sons_i} f_{k,j+1}\),这里以下行为例。

  • 显然这个转移炸了。有一个办法是长剖,见长剖优化 dp,但我们这里不使用它。

  • 考虑使用欧拉序式 dp,显然原问题是可差分的,因为只关心 \(w_{now}\) 一处的信息,故直接记录即可,时空都可接受。

  • 我不会 \(O(n+Q)\) 的求 lca(需要树分块),所以...还是 \(O(n\log n)\)

某 T1 树

posted @ 2023-01-09 16:59  未欣  阅读(114)  评论(0)    收藏  举报