MST的构造
Boruvka算法常用于解决这类问题:给你n个点,每个点有点权,任意两个点之间有边权,边权为两个点权用过某种计算方式得出,求最小生成树。动图
MST上的确定性和存在性问题
最小生成树的两个性质:
(1) 不同的最小生成树中,每种权值的边出现的个数是确定的
(2) 不同的生成树中,某一种权值的边连接完成后,形成的联通块状态是一样的
可以用这两个性质做最小生成树计数
Kruskal重构树
令 a , b a,b a,b路径上最长边最短: “最短的最长边”一定在MST上,所以我们求一下MST,再在MST上找 a , b a,b a,b路径上的最长边。
问 n n n个点 m m m条边的无向连通图上任两点的最短距离, m − n m-n m−n很小:随便建一棵生成树,把图看成树上挂几条边 CF1051F The Shortest Statement
trick: 对区间 [ l , r ] [l,r] [l,r]操作 ⇒ \Rightarrow ⇒ 连边 l → r + 1 l\to r+1 l→r+1
Boruvka算法简介:
对图中所有的点 i i i,找到 i i i连向其它点的最小边,如果这条边还没加进 M S T MST MST,就把它加上。执行完后,把每个连通块缩成点。
不断重复上面的操作,直到只剩下一个连通块。
时间复杂度 O ( ( m + n ) l o g n ) O((m+n)logn) O((m+n)logn)
此题中,要让 a i ⊕ a j a_i \oplus a_j ai⊕aj最小,就让 a i , a j a_i,a_j ai,aj的高位尽量保持相等。
假设我们现在运行Boruvka,并且目前只剩两个连通块,那么设 p p p表示所有的 a a a的第1~ ( p − 1 ) (p-1) (p−1)位(从高位数起)都相等,在第 p p p位才出现不同。
那么在这两个连通块中一定不会有 连接 i , j i,j i,j且 a [ i ] a[i] a[i]的第 p p p位和 a [ j ] a[j] a[j]的第 p p p位不同的边 ,也就是说 a a a值的第 p p p位为1的点构成一个连通块, a a a值的第 p p p位为0的点构成一个连通块。
那么最后加入的边一定是 连接 i , j i,j i,j且 a [ i ] a[i] a[i]的第 p p p位和 a [ j ] a[j] a[j]的第 p p p位不同的边,用 01Trie树 找到这样的最小边。
加完最后一条边,我们再递归回去,把 a a a值的第 p p p位为1的点连成一个连通块, a a a值的第 p p p位为0的点连成一个连通块。
Code
Code
根据贪心,把 S S S的费用全部用来降一条边的权值不比用来降多条边的权值劣。
枚举每一条边,看一下降这条边的答案是多少,最后取最优结果即可
先用Kruskal建出MST,设 s u m sum sum为MST里各边的权值和。
如果降树边:
答案为 s u m − ⌊ S c i ⌋ sum-\lfloor\frac{S}{c_i}\rfloor sum−⌊ciS⌋(保证降完后该边是在MST里的)
如果降非树边:
把降完权值后的非树边连上,原MST上出现一个环,我们找到环上最大的边删掉即为新的MST。
每条非树边对应的环上最大边边权可以用倍增预处理出来。
Code
Kruskal重构树
不会删边。所以考虑离线,按时间倒序进行操作,删边变成加边。
但是遇到的麻烦是,操作 1 是正序进行的,如果我们倒序操作,就不知道当前哪些点 p u = 0 p_u=0 pu=0 了。
解决方法是,先倒序遍历一遍所有操作,按“加边”的顺序,建出重构树。重构树优美的性质是,对任意一个节点 v,在某个时刻之前和它连通的节点,恰好在重构树上 v 的某个祖先的子树中。并且我们可以通过树上倍增,在 O ( l o g n ) O(logn) O(logn) 的时间内找到这个祖先。
建出重构树后,我们回到正向的时间线。按正序处理所有询问(操作 1)。前面说过,在某个时刻和 v 连通的节点,在 v 某个祖先的子树中。先倍增找到这个祖先。它的子树是 dfs 序上连续的一段。我们预处理出重构树的 dfs 序,那么问题转化为求区间最大值,支持单点修改。可以用线段树维护。
Code
法一:
先预处理出 d i s u dis_u disu表示 u u u到最近的充电中心的距离(求多源最短路戳这)
从 a a a到 b b b的路径 能经过边 ( u → v , w ) (u\to v,w) (u→v,w),当且仅当 c − d i s u − w ≥ d i s v c-dis_u-w\geq dis_v c−disu−w≥disv,即 d i s u + d i s v + w ≤ c dis_u+dis_v+w\leq c disu+disv+w≤c。
那么问题变成求一条从 a a a到 b b b的路径使得路径上每条边的 d i s u + d i s v + w dis_u+dis_v+w disu+disv+w的最大值最小(明显是满足条件的最小的 c c c)。
可以用Kruskal重构树实现,
也可以用NOIP2013货车运输/BZOJ3732 Network的套路实现:
“最短的最长边”一定在MST上,所以我们求一下MST,再在MST上找 a , b a,b a,b路径上的最长边。
法二:
在任意两个充电中心 i , j i,j i,j之间连边,边权 d i , j d_{i,j} di,j为原图上 i , j i,j i,j之间的最短距离。
那么 c c c为新图中从 a a a到 b b b的路径上最长边的最小值。法一中已经解决了这个问题。
现在的问题是如何求 d i , j d_{i,j} di,j,这里介绍一种剪枝方法:
先跑多源最短路,对每个点 i i i求出离 i i i最近的充电中心 f i f_i fi和到 f i f_i fi的最短距离 d i s i dis_i disi,然后枚举原图中的每条边 ( u → v , w ) (u\to v,w) (u→v,w),在新图上连边 f u → f v f_u\to f_v fu→fv,边权 w ′ w' w′为 d i s u + d i s v + w dis_u+dis_v+w disu+disv+w,
可以证明 m i n f u → f v { w ′ [ f u → f v ] } = d u , v min_{fu\to fv}\{w'[fu\to fv]\}=d_{u,v} minfu→fv{w′[fu→fv]}=du,v。同样的剪枝方法见这里。
Code
m − n < = 20 m-n<=20 m−n<=20,所以可以看成是一棵树上挂了几条边
树上求两点间最短距离用LCA
多出来的边怎么办?
找出所有非树边的端点记为特殊点,枚举u,v间路径过每一个特殊点的情况
(u,v路径要过非树边一定过特殊点,枚举过点的情况是因为可以用dijkstra)
因为u,v间路径只有 只过树边 和 不是只过树边 2种,所以一定不会漏(不保证不重,但没关系)
Code
Code