杂项
虚树
概述
-
虚树,即将树的一系列关键点及其 \(lca\) 取出得到的一棵反映这些点在原树上的关系的树。
-
容易证明,维护一个按 \(dfn\) 升序排序的优先队列,不断从队头取出两个元素求 \(lca\) 后放回直到队列中只剩一个元素,所得的所有点就是虚树的所有点。显然,这代表着虚树的点数是 \(\leqslant 2num\) 的,\(num\) 是关键点的个数。
-
故通过构建虚树,我们可以将原树的 \(n\) 个点缩减到 \(O(num)\) 个点。如果题目允许我们进行这样的变换,那么这大大降低了我们的复杂度,从 \(O(nQ)\) 降为 \(O(\sum num)\)(共有的复杂度被略去了)。
-
虚树有一个很关键的性质:虚树上任何一条边都是一条不分叉的路径。我是说,这条边(以及从上面挂出去的子树)一定只和两个虚树点有关系。这种简单性很多时候有益于对原树做某些统计。
构建
-
考虑使用单调栈。
-
将所有点按 \(dfn\) 升序排序。
-
维护一个深度单调递增的栈,事实上,我们希望里面维护的是一条“最右链”,即已经考虑过的点构成的虚树(该算法是逐步的,但因为要排序所以不是在线的)的最右一条链。
-
当新点到达时,求其与栈顶的 \(lca\)(强制包含根所以栈总是不空),接着进行这样的循环:
-
如果栈顶就是 \(lca\),结束。
-
将栈顶弹出并存起来,记为 \(tt\)。如果弹出后栈顶的深度小于 \(lca\) 的深度,则将 \(lca\) 压到栈顶。
-
从栈顶向 \(tt\) 连边,然后回到第一步。
-
-
-
这一构建的正确性主要基于 \(dfn\) 序。严谨的证明...我得承认,我不太会。
-
复杂度 \(O(n\log)\)。
P2495 [SDOI2011] 消耗战
-
题意:给出一棵边带权的树,求将若干个关键点与 \(1\) 分割开的最小代价。切割一条边的代价即为其边权。
-
数据范围:\(n\leqslant 2.5\times 10^5,\sum num\leqslant 5\times 10^5\)。总之是单 \(\log\) 可过的复杂度,后面不标了。
-
首先建虚树,虚树上边的边权为对应路径上的边的边权的最小值,这可以通过倍增预处理求得。
-
然后做树形 dp,设计如下:
-
状态设计:\(f_i\) 表示将 \(i\) 子树内所有关键点都和 \(1\) 分隔开的代价(只允许割断 \(i\) 子树内的边)。如果 \(i\) 本身是关键点,那么 \(f_i=+\infty\)。
-
初始化:\(f_i=\begin{cases}+\infty & i\in keys \\ 0 & else\end{cases}\),这里 \(keys\) 是关键点集合。
-
状态转移方程:\(f_i=\sum\limits_j \min(f_j,e_{i,j})\)。
-
-
复杂度瓶颈在于构建虚树,为 \(\sum\limits num\log\)。
P3233 [HNOI2014] 世界树
-
题意:求虚树上的每个点支配多少点,所谓支配...对于原树点 \(x\),支配其的点是和它距离最近的虚树点,如果有多个,则为编号较小的一个。
-
数据范围:\(n,q,\sum K\leqslant 3\times 10^5\)。
-
首先肯定建虚树。不妨将树用虚树划分为若干个部分来计算贡献,具体实现如下:
-
使用二元组 \((fr,d)\) 表示每个点被哪个点所支配,支配点到它的距离是多少。显然,虚树上点的支配二元组可以通过假换根 dp 处理出。
-
接下来对非虚树上点进行分讨。不妨考虑虚树点 \(now\) 的各个子树和父亲这些方向,发现子树方向分两种:
-
原树子树内无其他虚树点的:整个子树都受 \(now\) 支配。
-
还有:得切割一下。
-
先考虑前一种。显然我们不能碰原树点,哪怕是枚举一个虚树点的所有出边(菊花图笑看复杂度)。只能容斥,故不妨先将 \(x\) 子树内的点全部当成受它支配的,多的等下减回来。
-
然后考虑第二种。不妨先考虑 \(now\) 的儿子 \(to\) 对 \(now\) 的贡献,注意 \(now,to\) 都可能本身不是关键点(是一个 \(lca\))。利用倍增求 \(lca\) 的结构,找到分界点,不妨称为 \(belu,beld\),\(beld\) 子树内的不一定都归 \(to\) 但显然都不归 \(now\),故让 \(now\) 减去 \(beld\) 的子树大小。
-
回过头来考虑这对 \(to\) 的贡献(准确地说,是对 \(fr_{to}\) 的贡献,但这里略掉这种区别),显然 \(to\) 子树外,\(beld\) 子树内的点都受 \(to\) 支配,故这样减一下然后把贡献加到 \(to\) 上即可。
-
这里的 \(belu,beld\) 不是直接的路径中点分界。这里我们要考虑两者的支配二元组中 \(d\) 的影响:不妨考虑极限情况,两者都受一个极高的点支配,于是两者在划地盘的时候事实上应该全部划给上面。
-
事实上,不妨记 \(dis=d_{now}-d_{to}+e.l\),这即为把两者的 \(d\) 的影响消掉(某种程度上相当于把两者向上或向下拉了,将两者之间的路径拉长)。然后,从 \(to\) 开始向上走 \(\lfloor\frac{dis-1}{2}\rfloor\) 步,这会走到真中点下方(不含)的第一个点:注意,\(dis\) 为奇数时 \(now,to\) 可以直接分好点,为偶数时反而要对中点分讨,因为点数等于边数减一。
-
从这一式子能够看出,极限情况下 \(dis=2\times e.l\) 或 \(0\),于是会走到 \(now\) 下面一点点或者就在 \(to\) 本身,分析发现还是对的。
-
-
显然复杂度是对的。不算太恶心的分讨...这是因为我处理得好(叉腰,挺胸)!([数据删除]附注:这种心理活动没什么意思,属于是闲得想要显示一下。不过,这个括号内的内容也是,尤其是[数据删除]这个故弄玄虚的人称...虽然也确实有屏一下的理由。不过,就算没什么意思...笑)
22.11.8 T4 异常
-
题意:...简单起见,我们这里直接定义出上一道题的支配二元组。于是问题是,在对应虚树下求所有点的支配二元组的 \(d\) 的最大值。
-
数据范围:\(n,\sum K\leqslant 5\times 10^5\)。
-
感觉就是出题人没题出了,只好把世界树搬过来改了个细节。
-
那我们也这样把这道题做了...
-
(炎国粗口),计数可差分,最值还可差分吗?!
-
看来只能操作一下了。
-
每个点不是在原树意义下有一个边 vector 吗,预处理每个子树的 \(\max dep\),然后按 \(\max dep\) 把边排个序。注意这之后不用重新求 \(dfn\),因为我们其实只是需要随便一个 \(dfn\) 序,不一定非得和边的顺序对应。
-
在虚树上,还是分两类来计算贡献。我们希望首先通过枚举虚树上的出边来将对应方向的原树上的出边 ban 掉,然后暴力枚举原树上的出边,因为排过序所以至多失败虚树上的 \(deg\) 次,可以摊到虚树本身上。
-
一个自然的想法是把第二类也用类似的办法解决。但容易发现,\(belu\) 对其父亲的贡献不可计算。
-
虚树遇事不决,就问倍增。考虑设计一个倍增来解决这些问题,注意到我们需要 ban 一个方向(未必等于 ban 一个出度,考虑先单叉再多叉的情况),且对上行和下行都有需求,故不妨设计 \(up2_{i,j}\) 表示从 \(j\) 出发向上走 \(2^i\) 步,不考虑 \(j\) 及其子树,走过的所有点的子树中的所有点中,到 \(j\) 最远的点和 \(j\) 的距离是多少,并设计对称的 \(dw2_{i,j}\) 表示从 \(j\) 的 \(2^i\) 级祖先向下(向 \(j\) 的方向)走 \(2^i\) 步,不考虑 \(j\) 及其子树,走过的所有点的子树中的所有点中,到这个祖先最远的点到其距离是多少。
-
嗯,这显然是可结合从而可倍增的,我们只要从 \(beld\) 倍增到 \(now\) 下面以 \(dw2\)(刚好把 \(beld\) 的子树摘出去)对 \(now\) 贡献(这里有一个有趣的小细节:如果从下向上合并,则合并时会加 \(2^{step}\),但第一步加的量应该额外减一,因为不能走到 \(beld\) 本身),同理从 \(to\) 倍增到 \(beld\) 以 \(up2\) 对 \(to\) 贡献即可。注意有可能 \(beld\) 就在 \(now\) 下面或者 \(beld\) 就是 \(to\),走不动,不过无所谓。
-
-
OK。也不算太麻烦,一般麻烦吧。不过这个做法是第四版了。