树上问题是一类重要的问题,大体上可分为:
用数据结构,如树剖、LCT、树上倍增来维护路径/子树信息。
用长链剖分/启发式合并//点分治/边分治等方法快速合并各子树信息。
与其他算法结合,如树上高斯消元/树上莫队/树上斜率优化等。
口胡完毕。
然而树上问题往往千变万化,虽然方法就是这些,但是维护的东西不一样可能做法就千差万别,而且对于码力也有一定的要求。
这里是一个杂题集,慢慢的更题。
比如定个小目标,先把蒋队的题写完。
树上问题选讲 by JXL
幻想乡战略游戏
很容易可以相到点分树,因为点分树上一对父子所管理的区域是可以快速合并的,大概就是所有点到父亲的距离容斥掉自己这个区域点到父亲的距离和这样子,需要维护:
1.该区域的点到重心距离*权值和。
2.该区域点到重心父亲距离*权值和。
3.该区域点权值和。
code:
1 | // luogu-judger-enable-o2 |
考试T2
题意:给定两颗树,询问。
边分治第一颗树,两边黑白染色,在另一颗树上加上权之后便变成询问第二颗树上异色点对的带权距离和。
于是每次在第二颗树上建虚树后树上即可。
切记:多叉转二叉。
code:
1 |
|
小清新数据结构题
记录一种蛮巧妙的解法,考虑根恒为的做法,设根到点路径上点依次编号,表示以为根时的子树大小。那么改变量就是,把当做权值,维护点到根的链上权值和即可。
然后考虑换根为,发现路径上的点子树大小才发生改变,并且:
现在答案
消去和:
可以形如前面树剖维护即可。
code:
1 | // luogu-judger-enable-o2 |
天天爱跑步
首先链上加肯定树上查分,化出式子后发现每次要就是合并两个桶,答案就是当前桶某个位置的值。直接线段树合并即可。
code:
1 |
|
树上游戏
首先很容易想到一个点分治/边分治的做法,这里只记载一下的树上差分做法。考虑每个颜色的贡献,设删去所有该颜色后点的连通块大小为,因为只有跨连通块的会产生贡献,那么贡献就是。然后可以看出点与所处联通块基本相同,除了颜色为的联通块以外。所以我们可以维护每个点在切掉后的连通块大小(相当于是这个连通块的根),最后先处理出根的答案,每次换根修改即可。
暂时
code:
1 |
|
NOI2014购票
首先如果没有的限制,那么就是个树上斜率优化的板子了,具体操作就是从上往下转移,每次维护到根的那条链的凸包,用二分插入来支持回溯,至于斜率不单调只要凸包上二分即可。但是这个的限制很恶心,有一种思路很简单的做法,就是维护一棵以深度为下标的线段树,线段树上每个节点是一段链组成的凸包,然后找到当前点对应的区间,拎出来个凸包后分别在上面二分即可,复杂度是。
这里有两个坑爹的细节,第一就是和在不给变量赋值的情况下,他们的初值是不一样的,差不多是个级别,而是级别,自闭到怀疑评测机抽风了;第二就是因为每次要回溯线段树上个节点,所以回溯用的栈要开。
少有的写了一次指针开不等长数组(左闭右开不符合我的习惯)
code:
1 |
|
APIO2018铁人两项
圆方树裸题,对于这种"两点之间简单路径"的统计大多是圆方树,因为一个两个点之间的简单路径能包含的元素就是这两点间所有点双内的点。然后看此题的贡献就是这些点双的和,即方点权值和,不过要减去割点(因为作为圆点被两个方点连了),还要减去起始点和终止点。
这里一个巧妙的方法就是直接把圆点权值设成,然后直接求所有"起终点都为圆点"的路径权值和即可。
code:
1 |
|
SDOI2018战略游戏
贡献就是虚树上每条边上圆点的个数和。
CF487 E. Tourists
带修改圆方树。我们设方点为周围一圈圆点的最小权值,即该点双中的最小权值,然后要求的就是圆方树上两个圆点间的最小权值,这个可以树剖实现。带修改的实现很巧妙,因为如果修改一个圆点就暴力更新周围一圈方点显然复杂度是不对的,我们使每个方点只维护其儿子圆点,不维护父亲圆点,当查询两个圆点的是方点的时候直接特判加上父亲圆点即可,这样修改圆点只用修改父亲方点的权值。
当时脑子一抽:我要维护一个数据结构,支持动态插入、删除、查询集合内最小值,动态开点权值线段树!后来发现这就是个堆(这种错误有可能会要了老命),最后为了方便还是写了。
code:
1 |
|
HNOI2016网络
首先可能会被线段树分治误导,但是这里我们是可以处理删除的,所以不要惯性思维,老是看见个"某个限制有持续时间"就想线段树分治。考虑答案是具有二分性的,"至少有一条的边不经过""不是所有的边都经过"。然后发现这个东西是可以整体二分的,按时间加的边,然后每次查询是否所有边都经过,是的话扔左边,否则扔右边。这里要用到链上加转单点加的,这样复杂度就是。
code:
1 |
|
SDOI2013森林
讲题现场都能想到的题就不写了吧,就是暴力的启发式合并重构主席树即可,除了码没什么价值。
PKUSC2019村庄
讲题时候猜测了一下就是隔板间逆序对个数,那么先暴力扫求出块板还在的时候的答案,每次拆除隔板直接线段树合并即可。
BZOJ3451Normal
权限题。
首先各点地位相同,把复杂度摊到每个点上,就是每个点期望的点分树祖先个数,又可以拆成,而这个概率就是,即这条有向路径上点的个数分之一,相当于在点分这条路径时,如果直接选中重心为,那么是的祖先,否则不是。然后问题变成求长分别为的路径数量。
考虑点分治,将自己的深度数组卷一下就是经过该点的有向路径的多项式,但是其中有些不是简单路径,是不合法的,这些都是来自同一颗子树中的两项卷了起来,所以在容斥掉子树卷积后的数组即可。因为多项式次数不会超过点的两倍,所以复杂度是的。
code:
1 |
|
WC2018通道
首先考虑边分第一颗树,对第二颗树建虚树,设,其中是到边分的边的距离,第二颗树的虚树,那么相当于枚举前面都被控制了,相当于给第三颗树上每个点挂了一条边,权值为,然后枚举,对左右子树黑白染色,询问第三颗树上的最远异色点对。
这里有个结论:两个点树上集合并后的直径必定是原先两个点集直径上的四个点取其中两个。
然后就是维护虚树上每个点子树内的直径点对,每次合并就分四种情况讨论就可以了(交叉相配)。
code:
咕咕咕。
PKUWC2018 Minimax
首先权值是可以离散的,那个也可以直接求,所以我们要做的就是统计根取每个值得概率。线段树维护以排名为下标的概率,转移如下
每次线段树合并即可,当遇到只有某一边的节点时,说明在另一边比它们大的集合和比它们小的集合
都是一样的,即后面那一坨是一样的,区间乘法即可,依靠前序遍历记下比这些数数小的集合,最后扫一遍根的线段树统计答案。
另外对于这种不好调试的概率取模,可以通过查询是否为检验自己是否写错了。
还有就是线段树合并在有标记的时候要先下传标记.
code:
1 |
|
杂题
共价大爷游长沙
神仙题目,有一个神奇的想法,给每条路径异或一个随机权值,若某条边的权值等于在场所有权值的异或和则它是关键的。为什么是异或而不是其他运算?因为方便修改,这种将树上路径“别”过来的操作大多都是异或,并且对于操作,异或两次就是撤销。如果新加边,减边,若权值是,那么我们将新图中的路径异或,等价于把经过的路径都别过来了。于是我们要做的就是边转点,用支持路径异或,单点求值即可。
code:
1 |
|
树剖/LCT维护动态DP
树剖:
每个元素等于轻儿子矩阵乘自己矩阵,维护重链矩阵积,每次修改的时候进行:改重链、找它作为轻链的位置(链顶)、修改父亲矩阵、改父亲重链,反复进行。记录链底,查询就查询根所在重链。复杂度
code:
1 | // luogu-judger-enable-o2 |
LCT:
每个元素等于虚儿子矩阵乘自己矩阵,修改就是在的时候把新的虚儿子加上,新的实儿子减去。查询就是把原树的根转到根,然后查询根在的实链的矩阵积(就是那颗的根的矩阵)。这里特别要注意的是因为转移是深度小的主动合并深度大的,所以要先,然后再。
code:
1 |
|
NOI2014魔法森林
权值按第一维排序,动态维护第二维最小生成树,边权转点权,维护链上最值即可。
水管局长
离线询问,倒序变加边,维护最小生成树。
大融合
维护子树信息。