Kruskal 重构树
本次是涩!图!

Kruskal 重构树
Kruskal 最小生成树的扩展。适用于处理与瓶颈路有关的问题。
天依宝宝可爱!
洛谷 P4768
思维难度:\(\color{#52C41A} 绿\) *1600
没想到真能做到「关于 SPFA,它死了」的经典题目。真的是从来没想过的。
先给出几个定义:
- 最小瓶颈树:在一个图中,若存在一棵生成树使得生成树上的所有边权都 \(\le x\) 且 \(x\) 最小,则称这棵生成树是该图最小瓶颈树。不难发现最小生成树 \(\subseteq\) 最小瓶颈树,且最小瓶颈树不一定唯一。
- 最小瓶颈路:在一个图上,对于一个值 \(x\),若存在一条路径 \(u \leadsto v\) 上的所有边权都 \(\le x\) 且 \(x\) 最小,则称这条路径为 \((u,v)\) 的最小瓶颈路。同理最最短路 \(\subseteq\) 最小瓶颈路,且最小瓶颈路不一定唯一。
回到这个题,简要题意:
给定一个无向图。对于每条边有边权 \(l\) 和海拔 \(a\)。
\(q\) 次询问,每次给出起点 \(u\) 和水位线 \(p\),可以先不消耗代价地走任意条海拔 \(> p\) 的边,之后经过的每条边都需要消耗边权的代价。对于每次询问,最小化走到点 \(1\) 的代价。
强制在线。
\(n \le 2 \times 10^5\),\(m,q \le 4 \times 10^5\)。
显然可以先跑个单源最短路处理出 \(1\) 到每个点的最短路,然后问题就转化成了求与 \(u\) 只用 \(a_v > p\) 的边相连的点 \(v\) 的 \(\min \{ dis_v \}\)。
先考虑离线怎么做,显然可以把询问按 \(p\) 从大到小排序,每次只加 \(a_v > p\) 的边,拿 dsu 维护连通块内最小值就可以了。
那么考虑离线转在线。常见的思路是把离线的过程记录下来。
可以发现,上面的过程就相当于求最大生成树的过程。
于是自然地引出了重构树的概念。所谓重构树,就是把 Kruskal 的过程记录下来。当每次合并两个连通块时,把这两个连通块看成重构树上的点 \(u,v\),然后新建一个点 \(p\) 为 \(u,v\) 的父亲(表示合并之后的连通块),\(p\) 的点权就是所加边的边权。
这样就得到了一课拥有 \(n\) 个叶子结点的完全二叉树,它就叫做 Kruskal 重构树。
那么这棵树有什么用呢?可以发现,对于两个原图上的点 \(u,v\),它们在重构树上的 lca 的点权就是最小瓶颈路的最大边权!
所以,回到原问题,一个点 \(u\) 可以直接到达的所有点就容易求了,考虑 \(u\) 的一个祖先 \(x\) 满足 \(x\) 的点权 \(> p\) 且 \(fa_x\) 的点权 \(\le p\),那么子树 \(x\) 中的所有叶子即为所求。有了这个,直接在重构树上维护每个子树的最小 \(dis\),倍增求 \(u\) 的祖先 \(x\) 即可。
于是这个题就做完了,复杂度 \(O(n \log n)\)。
我认为 TLE 最难查出错来的是望删 cerr。
天依宝宝可爱!
LOJ 137
思维难度:\(\color{#FE4C61} 红\) *800
其实这个才是真板子题(
不过带 log 的 LCA 被卡了(也可能是我常数太大了),一直多跑 <=10ms,被迫上 dfn 了。
天依宝宝可爱!
洛谷 P4197
思维难度:\(\color{#F39C11} 橙\) *900
显然倍增找祖先,在重构树后重新排序的序列(叶子节点)上求区间第 \(k\) 大,但是我不会主席树,所以做不了。
天依宝宝可爱!
AT_agc002_d
思维难度:\(\color{#F39C11} 橙\) *1000
在重构树上二分答案就可以,每次 check \(O(\log n)\) 查最远祖先的子树大小。总共两只 log。
注意:
- 算子树大小的时候特判 \(u = v\) 的情况。
- check 函数中的 \(u,v\) 不能用全局变量,因为在跳祖先之后会改变。
- 二分上界是 \(m\) 而不是 \(n\)。
天依宝宝可爱!
洛谷 P3684
思维难度:\(\color{#FFC116} 黄\) *1400
路径上的最大边权最小,显然是瓶颈路问题。
先预处理出来每个点能放置的最大正方形,然后相邻点连边,边权为两个点最大正方形的 min,然后直接跑最小瓶颈路就可以了。
调了一下午的死因:预处理最大正方形的时候,只考虑了纵向横向两列,没考虑整个正方形(,最后还是 chatGPT 帮我找出错误来的 xd。chatGPT 强啊!
天依宝宝可爱!
CF1628E
思维难度:\(\color{#52C41A} 绿\) *1700
显然询问相当于在最大重构树上 \(x\) 与一个点集中每个点的 lca 的最大值(令这个点为 \(u\))。但是可以发现这样很难做,因为对于每个 \(x\),所对应的 \(u\) 没有什么规律可寻,而且因为是区间操作,也不好操作分块。
所以可以倒过来想,考虑转化为最小重构树上 \(x\) 与一个点集的 lca,这样只需要维护这个点集的 lca 就可以了,简单了很多。
这里一个很显然很有用不过有点难想到的结论:
一个点集的 lca 为这个点集中 dfn 最大的点和 dfn 最小的点的 lca。
于是线段树维护 dfn 的 min/max 就解决了。
太久没写线段树导致的,build 中非叶子节点的 tag 忘记初始化了xd
天依宝宝可爱!
CF1706E
思维难度:\(\color{#FFC116} 黄\) *1300
上面那题的弱化版。
不过还有一个更简单(好像也更好想)的做法,把 \([l,r]\) 连通拆成若干个 \([x,x+1]\) 的小区间连通,于是预处理所有 \([x,x+1]\) 的 lca 然后 ST 表维护 max 就行了。
知道错了,以后像这种有部分数组需要开 2 倍而且数组很多的,数组都开 2 倍。还有多测无论需不需要全部清空 /kel
天依宝宝可爱!
洛谷 P4899
思维难度:\(\color{#52C41A} 绿\) *1600
容易想到建两课重构树(点权为原图上边的两个端点的 min/max),一个最大一个最小,于是在最大重构树上跳 \(s\) 的祖先直到 \(val_{anc} < l\),同理在最小重构树上跳 \(t\) 的祖先直到 \(val_{anc} > r\)。
然后问题就转化成了判断两个子树对应的叶子结点是否有交。
因为是子树,所以考虑 dfn,因为 dfn 是连续的。那么就转化成了对于给定的两个序列 \(a,b\),判断 \(a_{l_1 \sim r_1}\) 和 \(b_{l_2 \sim r_2}\) 是否有交。
然后就不会了。
注意到这很像是一个二维数点的形式,所以考虑离线下来,以 \(a_i\) 为 \(x\) 坐标,\(b_i\) 为 \(y\) 坐标,于是只需要数矩形内点的个数就可以了,这是经典二维数点问题。
硬控一晚上 + 半个上午的死因:边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!边数是 \(m\) 而不是 \(n\)!
天依宝宝可爱!

浙公网安备 33010602011771号