前言
温馨提示:本文将会讲述树链剖分(会提到 LCT 的做法)和全局平衡二叉树两种方法,所以强烈建议两种方法都想学习的读者仔细阅读完本文!(真不要脸)
动态 DP,它的模板题之所以和动态树分治放在一起,是因为它们有两个共同特点:
- 它们在没有修改时都是用于维护树上信息的
- 它们都是带修改的,即动态的
所以,动态 DP 一般是用于解决动态维护树上信息的问题
这类问题最初只是可爱的树形 DP,却被丧心病狂的出题人套上了修改操作,褪去了原本善良的模样
那么,在学习动态 DP 之前,你需要掌握以下前置知识:
DP- 矩阵乘法(以及其对 DP 的优化)
- 树链剖分(包括链上的线段树操作)
- 二叉查找树(亦称二叉排序树)
阅读完以上内容,我们就开始吧!
正文
放上模板题(简单版):P4719 【模板】“动态 DP”&动态树分治
我们先来考虑这样一件事情:如果没有修改,这道题你应该怎么做?
(大概是人人都会吧,要不然你也不会点进来看到这句话)
我们可以设 f u , 0 / 1 f_{u,0/1} fu,0/1 表示如果点 u u u 不选/选,该子树的答案是多少
那么显然有转移方程:(点 v v v 为点 u u u 的子节点, v a l u val_u valu 为点 u u u 的点权)
f u , 0 = ∑ max ( f v , 0 , f v , 1 ) f u , 1 = v a l u + ∑ f v , 0 \begin{aligned} & f_{u, 0} = \sum \max(f_{v, 0}, f_{v, 1}) \\ & f_{u, 1} = val_u + \sum f_{v, 0} \end{aligned} fu,0=∑max(fv,0,fv,1)fu,1=valu+∑fv,0
临界情况显然:对于叶子节点 l l l,有 f l , 0 = 0 , f l , 1 = v a l l f_{l, 0} = 0, f_{l, 1} = val_l fl,0=0,fl,1=vall
好,现在我们已经会 m = 0 m = 0 m=0 的部分分了!(然而这题并没有给,太不照顾作者这样的蒟蒻了)
接下来我们考虑带修
可以发现单点的修改操作最多只会修改一条链上的 f f f 值,所以我们可以想到用树链剖分维护树上的信息
为什么用树链剖分?
- 众所周知,一个点到根节点的路径上,至多只会经过 log n \log_n logn 数量级的重链,这就为时间复杂度提供了保证
- 众所周知,重链的底端都是从叶子节点,那么做 DP 时我们不需要花额外的力气去为其设置初始状态,同时也保证了树形结构自底向上转移的优美性
- 众所周知,树链剖分的优美性在于破树为序,所有的操作在维护的 DFS 序上进行,因此我们可以很方便地用修改序列的方式修改树
That’s why I choose 树链剖分!
为了迎合轻重儿子的设定,我们再设一个 g u , 0 / 1 g_{u, 0/1} gu,0/1,其中 g u , 0 g_{u, 0} gu,0 表示点 u u u 的所有轻儿子可选可不选~~(任其生死)~~的轻子树答案之和, g u , 1 g_{u, 1} gu,1 表示点 u u u 的所有轻儿子都不选的轻子树答案之和(下面的转移方程中为了保持结构优美性,把 v a l u val_u valu 加到了 g u , 1 g_{u, 1} gu,1 中)
所以状态转移方程就可以更新为:(此时点 v v v 为点 u u u 的重儿子)
f u , 0 = g u , 0 + max ( f v , 0 , f v , 1 ) f u , 1 = g u , 1 + f v , 0 \begin{aligned} f_{u, 0} &= g_{u, 0} + \max(f_{v, 0}, f_{v, 1}) \\ f_{u, 1} &= g_{u, 1} + f_{v, 0} \end{aligned} fu,0fu,1=gu,0+max(fv,0,fv,1)=gu,1+fv,0
(g 临界情况同 f)
结构是不是优美许多了?至少不带求和符号了
那么,如果我们能够把 DP 结构化,使得我们每次修改只是修改一个地方的转移状态而不关心它的 DP 值具体修改成了什么,那么我们就能够用树剖的链上修改和查询(实质是线段树的单点修改和区间查询)来完成这些操作
说到 DP 结构化,就不得不提到一个东西——矩阵乘法
既然第二维有 0 / 1 0/1 0/1 两种状态,那我们设出这个状态矩阵:
[ f u , 0 f u