Codeforces 516D

原题链接

题意

给定一棵大小为\(n\),带边权的树,定义\(f(i)\)\(i\)与其它点的距离最大值。

\(m\)组询问,每次询问给定\(g\),求最大的联通块大小,该联通块满足各点的\(f(i)\)的极差不超过\(g\)

\(1 \leq n \leq 10 ^5 , 1 \leq m \leq 100\)。各边边权、\(g\)大小在\(int\)范围内。

题解

考场上我是这么想的,首先将\(f(i)\)单独求出,仅将其看作普通的独立点权。将各点按照\(f\)值从小到大排序,从小到大枚举\(f\)值最小的点\(i\),则一路扫过去,\(f(i) + g \leq f(j)\)的点都可取;随着\(i\)的右移,\(j\)也单调右移。指针的移动过程复杂度是\(O(n)\)的,问题在于如何快速地求解当前情况下的最大联通块大小。

考虑动态维护联通块大小。加点时维护联通块大小,并不难办,用并查集即可。然而随着\(i\)的右移,我们会删点,删点就会影响整个联通块的连通性,即可能将原联通块分裂成若干小块。这样一来,并查集无法维护,怎么做?

实际上,我将\(f(i)\)的本质跟所求完全割裂开来的想法不够妥当,这样就无法利用\(f(i)\)本身的性质进行求解。

一种不错的思路是设计一种删点的顺序,使得删点不改变联通块的连通性,则可用并查集维护。

这就与\(f(i)\)本身的性质有关了。感性理解一下,\(f(i)\)越大,在树中就分布得越边缘,删去它越不会影响连通性。

因此,只需将各点按照\(f(i)\)从大到小的顺序排列,双指针扫描,并查集维护即可。

时间复杂度\(O(nlogn + mnlogn)\)

一些细节

对于\(f(i)\)的快速求解,做法不少。这里简述几种。

法一

求点\(i\)到树上其它点最大距离。此处有1个结论,该目标点一定为树的直径两端点之一。因此可以两次DFS先求出直径的端点\(u,v\),对于每个\(i\)\(f(i) = min(dis(i,u),dis(i,v))\)。求解距离要求\(lca\),结合倍增,复杂度为\(O(nlogn)\)

法二(from AThousandMoon

考虑\(f(i)\)本义,\(f(i) = dep(i) + max(dep(j) - 2 \times dep(lca(i,j)))\)

求解\(f(i)\),即令附加值\(ext(i) = dep(j) - 2 \times dep(lca(i,j))\)最大。

我们对树进行DFS,考虑以\(x\)\(lca\)的情况对\(x\)的子树中各点的贡献。两个结点以\(x\)\(lca\),当且仅当它们属于\(x\)的两个不同的儿子。因此,对于\(x\)子树中的节点\(u\)\(ext(u) = max(ext(u),max(dep(v)) - 2 \times dep(x))\),其中\(u,v\)属于\(x\)的两个不同儿子。求\(max(dep(v))\)其实很容易,只是如果逐一更新\(ext(u)\),复杂度不能承受。注意到对于同一儿子子树中的各点,\(max(dep(v))\)都是一样的,且仅在最后时刻查询,故采用延时标记的方法;即将标记打在子树的根上,最后通过DFS将标记下传与求解。时间复杂度\(O(n)\)

[代码见此](https://github.com/littlewyy/OI/blob/master/cf 516D)

另解

首先求出所有\(f(i)\)。结合\(f(i)\)的性质可以发现,\(f(i)\)相近的点一定是连在一起的;且如果我们\(f(i)\)最小的点作为根节点,则满足对于任意节点,它的\(f\)值小于等于其子树中任意点的\(f\)值。

因此问题转化为,对于每个节点,询问其子树内\(f\)值小于等于\(f(i) + L\)的元素个数。使用平衡树+启发式合并即可解决,时间复杂度\(O(nlog^2n)\)

posted @ 2019-10-22 13:03  littlewyy  阅读(407)  评论(0编辑  收藏  举报