注意到最优解一定会走最小(瓶颈)生成树上的边,因此考虑建出合并树(Kruskal 重构树)求解。该树从根到叶子权值(原图边编号)w_i 递减,子树内叶子(原图点)数 s_i 也递减。所求即最小 t 使得 x,y 两点跳到 w\le t 的最远祖先后,两子树的并集内叶子数量达到 z,有以下三种做法。
Sol.5
直接二分 t 的取值,再用倍增将两点跳到 w\le mid 的最远祖先,求一下当前叶子数量即可。这里若 x,y 在同一连通块内则一定会跳到同一点,容易特判。由于二分和倍增都很满,该做法在所有做法中跑得最慢。
Sol.6
据说该做法是提出了无数 trick 的 gqh 提出的,因此又称 "gqh trick"。
上个做法无法直接倍增是因为两点跳 2^k 步时,所到点的 w 值没有任何规律,难以进行。考虑转为倍增到权值最大的一组不满足 z 限制的点,方法为对 x,y 分别维护倍增位置 kx,ky,每次跳到 x 的 2^{kx} 级祖先和 y 的 2^{ky} 级祖先,并统计两子树并集内叶子数量。若已满足限制,则至少有一个点跳多了,因此要求权值较大的点不能跳到,其 k 值减一;若不满足限制,则至少有一个点跳少了或恰好够,因此将权值较小的点跳过去,并将 k 值减一。
这样最终确定 x,y 后,\min(w_{fa_x},w_{fa_y}) 即为答案。任意一点往上跳一步就合法是显然的,否则可以跳得更高。然而有时会出现 w_x>w_{fa_y} 这样的情况,但此时答案只取 w_{fa_y} 仍正确。这是因为若 y 没有成功跳到 fa_y,一定是因为在 fa_y 时跟另一边已满足限制,且 w_{fa_u} 比另一边大,这样才会强制 y 不能跳上去。因此一定存在以 w_{fa_y} 为较大值的合法方案,w_{fa_x} 也同理,因此这个答案是对的。
与上个做法截然相反,本做法比较暴力。考虑将 Sol.5 中的 s 全放到对应的 w 处,也就是把每个点到根路径上的所有 (w,s) 放到 w 处,并直接对 w 二分,找到两边前缀最大值之和满足限制的最小 w 即为答案。处理方式是维护主席树,每个点从其父亲继承过来,再加入自己的信息即可。二分时由于主席树结构相同,可直接在两棵树上同时二分。
注意特判两个点在同一连通块内的情况,方式是在线段树上维护每个区间对应的树上节点,在上一步二分出结果后取出对应的两个点,判断其是否有祖先关系,没有则答案正确,有则需继续二分出单个值不低于 z 的最小值作为答案。这里从头开始二分即可,前面部分对答案没有影响。
Sol.8-9
考虑只询问单点连通块大小何时不低于 z,可以使用警报器。把询问挂在点上,并查集合并时也把所有警报器启发式合并,然后按 z 从小到大检查每个警报是否触发,若触发则该询问答案为当前边边权,删除该警报器;否则后面也不会触发,直接停止检查。这样总检查次数就是线性的了。为了维护从小到大的顺序,需要用 set 或堆维护所有警报,总时间复杂度是 O(n\log^2n) 的。本题需要维护两个点的警报,有以下两种做法。
注意特判两个点在同一连通块内的情况,方式是在警报触发时先判断目前是否在同一连通块,若已在则转而加入上面说的单点警报器,注意与两点警报器区分。由于每个询问最多同时存在两个警报器,每个时刻警报器总数都是线性的,启发式合并的复杂度不变。该过程可使用 set 维护,由于堆不支持删除任意元素,只能用可删堆代替 set 减小常数,上面表格中的代码使用了可删堆。
Sol.9
考虑优化折半警报器,发现其触发的条件比较严苛,导致必须按值从小到大维护。因此考虑将限制放宽,对 s_x,s_y 找到最大的 p 使得 f(p,s_x)+f(p,s_y)<z,其中 f(p,i) 表示大于 i 的数中最小的 2^p 的倍数。将 p 的警报放在 x,y 目前连通块上,在该连通块大小增大过程中经过 2^p 的倍数时触发。增大过程中经过是指 s 增大到 s' 的过程中,存在 2^p\mid t 满足 s<t\le s'。警报触发后同样删除、检查,再计算出新的 p 放回去。由于 p 不增,直接不断减小并检查是否合法即可。
现在需要证明误报次数是 O(\log n) 级别的。考虑转而证明一个 p 误报 O(1) 次就会变小,由于 f(p,s_x)+f(p,s_y)<z 且 f(p+1,s_x)+f(p+1,s_y)\ge z,因此 z-(s_x+s_y)\le 2\times 2^{p+1}= 2^{p+2},又由于 s_x,s_y 第二次及之后触发警报 p 时均增大了至少 2^p,因此同一个 p 触发 6 次时一定求出了答案,即最多误报 5 次。好像可以证明到不超过 3 次,不会。