P3920 [WC2014]紫荆花之恋
耗时4.5h的大工程。
这题真的不难
树上路径问题首先想点分治。并且这道题需要支持动态加叶子。如果动态加叶子的话,可能会使得点分数不够“平衡”。我们可以模仿 K-D Tree 的方法(类似替罪羊树),如果某个叶子节点占其父亲的 \(siz\) 的比值超过了 \(alpha\),那么我们就将这个子树暴力拍扁,重新构建。由于一次暴力拍扁是 \(O(nlog^2n)\) 的,因此我们的 \(alpha\) 值需要设得稍微大一些(比如 \(0.8\))。
具体来说,这道题要对新加的点 \(i\),找到符合 \(dis(i,j) <= r_i + r_j\) 的所有 \(j\)。我们考虑在 \(i,j\) 路径经过的分治重心上计算贡献,则有:
\[dis(i, rt) + dis(j, rt) <= r_i + r_j
\]
\[dis(j, rt) - r_j <= r_i - dis(i, rt)
\]
按照惯例,我们设 \(f1(i)\) 数组表示分治区域内各个点到分治重心 \(i\) 的距离 - 点权 的权值数组;\(f2(i)\) 数组表示分治区域内各个点到分治重心的点分树上父节点 \(fa(i)\) 的距离 - 点权 的权值数组,每次每层容斥求解,然后将\(i\) 的信息插入到 \(f1\), \(f2\) 数组里。 \(f1, f2\) 数组用平衡树维护。这里我用的替罪羊树(好写且常数小)。
针对插入叶节点,我们就像上面一样一直跳点分树的 \(fa\),计算并更新平衡树。如果我们计算贡献的时候发现这个点在点分树上的子树不平衡,那么我们就将这个点暴力拍扁,即重新做一遍普通的点分治。
细节比较多,比如说,显然这题要维护点分树,在拍扁以后我们可能需要把点分树的子树暴力重构,把子树和原来的父节点之间的边删除换掉。这个换边操作可以直接用 \(vector\) 实现,找到与 \(fa\) 连接的子树上的节点,更改 \(vector\) 里面存的编号即可。