生成树
求解最小生成树有三种常见算法:\(kruscal\),\(krim\),\(boruvka\)
大多数情况写好写的 \(kruscal\) 即可,复杂度 \(mlogm\)
当边数近似于完全图,将边排序会 \(T\) 时用 \(prim\),复杂度 \(mlogn\)
另一个 \(boruvka\) 算法的流程是这样的:
维护图中剩余的连通块,枚举图中每一条边,让每个点选择一个最优出边,然后合并最优出边,这样每次合并连通块数最少减少一半,复杂度 \(mlogn\),其性质好在只会合并 \(logn\) 次,与数据结构结合时有很好的性质
当然,一般明显的最小生成树是比较好做的,然而一些题会隐匿得比较深
最小生成树一般等价于 \(max\) 和 \(min\) 问题,两点间路径最大值最小问题可以转化为最小生成树上的路径(最短路对应和问题)
比如这道题,就是直接枚举倍数求取最小生成树即可
比如这道题就藏得很深,而且这是完全图的典型,必须用 \(prim\)
这是一个对于本质的阐释
对于条件三其实本质上就是求最小生成树上的最大值
对于完全图,一般只可能是 \(prim\) 和 \(boruvka\),这个题就是点名写 \(boruvka\) 了
其优势在于,算法都分轮,每轮需要知道点信息,而 \(prim\) 是 \(n\) 轮,\(boruvka\) 是 \(logn\),这样每轮中间后者都可以离线,解决了无避免的 \(n^2\) 空间问题
于是每一轮采用扫描线的形式,扫过这个点后便可以知道答案了
由于要求的是连通块外的最近的点,因此线段树维护最大值与最小值即可
按照权值排序所有天数后,接下来要做的就是快速找到这条链上非禁止的异色连通块然后连接
考虑怎样找才能使每次找到的颜色一定会连接上呢
其实可以发现暴力又是对的!
只需要在树上保存原树上的每个连通块,虽然同种颜色会被分割成多个部分,但是没有关系,除非它们被禁止了它们马上就要成一家人了
再来考虑禁止部分,发现每次枚举到但没有合并的连通块一定是等于禁止个数的,那么复杂度仍然正确
还有一种比较巧妙的方式是对于那张禁止的图,选出度数最小的点 \(u\),把图分成和 \(u\) 相连的点 \(S\) 和不相邻的 \(T\)
有性质是 \(|S|\le \sqrt{k}\)
那么这样就允许我们将 \(u\) 和 \(T\) 分别合并,然后 \(O(|S|^2)\) 暴力枚举,复杂度为 \(O(K)\) 正确
还是使用 \(Boruvka\)
考虑每一轮要找到这个连通块中最大的出边是什么,也就是拿着这个连通块中的每个点去寻找边权最大的异色连通块的边
考虑用 \(trie\) 树维护,那么在每个节点上维护出子树内编号最大值与最小值,判断是不是与自己相同就知道有没有走下去的必要
但是注意到这次的操作是与运算,如果朴素地在 \(trie\) 树上跑复杂度是不对的
那么我们暴力地把每个 \(1\) 子树合并到 \(0\) 子树内,相当于左子树为 \(0,1\) 共同的信息,在询问串为 \(0\) 的时候递归,右边同理,总之变成每次递归一边
当然这道题出成与运算以后不小心弄巧成拙,产生了更简单的做法
我们来模拟 \(kurscal\) 的过程,从大往小枚举点的权值,那么目前能形成的最大的边权就是这个点权了
那么枚举所有这个点权的超集来合并即可,当然只需要枚举其中一位改变的值即可,因为这样倒序枚举的过程实际上是向下合并的过程
这个当然也可以像上一个题那样做,但是有更简洁的做法
考虑将所有串都插入 \(trie\) 树,最优方式一定是每次选择 \(lca\) 最小的两个连边
但是我们发现其实插入以后有两个 \(lca\) 的节点正好有 \(n-1\) 个,那么反过来从 \(lca\) 寻找两个子树间最小的一条连接起来就好了
kruscal 重构树
重构树是将路径最值问题转化为 \(lca\) 问题的工具
模拟 \(kruscal\) 的过程,对于一条边 \(w(u,v)\),将其化为点,然后两个儿子分别为 \(fa(u)\) 和 \(fa(v)\)
那么这样所有原图的点是重构树的叶节点,而所有边是具有点权的节点,且有两个儿子
那么就达成了目的:两点间路径最值为两点的 \(lca\)
另一个性质是保留小于等于 \(x\) 的所有节点形成的图是重构树上点权小于 \(x\) 的点子树形成的
比如这道题就是一个很明显的重构树,每天找到第一个没被淹的 \(v\) 号点的祖先,那么这个点子树内的所有叶子节点都是可以不花费直接到达的,然后统计一下子树内与 \(1\) 号点的最短路即可
然而万万没想到,重构树还有升级版:
由于这道题维护的信息都在点上,那么需要来一棵关于点的重构树,使得两点的 \(lca\) 是路径最值
维护方法同样是枚举出边,然后并查集认父亲
总之,对于维护路径上最值的题多想想最小生成树,多想想重构树~
首先转化一下思路,变成到达每个点后加上这个点的 \(b_u\),那么设 \(c_u=max(a_u-b_u,0)\),只要保证 \(c_u\) 满足即可
于是考虑按 \(c\) 重构建树,在树上进行 \(dp\)
设 \(f_u\) 表示从父亲到达 \(u\),遍历完其他儿子后,进入某个儿子并结束的最小代价,那么答案就是 \(f_1\)
转移是 \(f_u=min(s_u-s_v+max(c_u,f_v))\),其中 \(s\) 是 \(b\) 之和
变形生成树
- 次小生成树
这个维护的方式很简单,首先跑出最小生成树,然后在上面倍增或树剖,枚举每一条没用过的边,然后查找树上两点间路径的的最大值然后替换,如果是严格次小需要 \(update\) 的时候特判
而在最小生成树上维护似乎是个常见套路,比如 这道题
- 最小差值生成树
这个没辙,得上 \(LCT\),关键在于维护树形结构的连通性
- 最小乘积生成树
似乎有点难,下次来了再学吧
P5540 [BalkanOI2011] timeismoney | 最小乘积生成树
题解
- 曼哈顿生成树
有结论是对于每个 \(45°\) 范围内连边最近的点
而最近的点等价于 \((x+y)-(x_0+y_0)\) 最小,并且有关于位置的限制,树状数组维护即可