1 条题解

  • 0
    @ 2025-8-24 22:33:55

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar lzyqwq
    我永远喜欢数据结构。

    搬运于2025-08-24 22:33:55,当前版本为作者最后更新于2023-11-12 10:39:13,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    cnblogs

    lxl 上课讲的题,来写个题解。

    样例很强,赞美 lxl!青蛙,呱 ????。

    $\text{rldcot} = \text{range lca depth count on tree}$。/yiw(猜的)。

    题目传送门

    • 给出一棵 nn 个点的有根树。定义 LCA(x,y)\text{LCA}(x,y)x,yx,y 两点树上的最近公共祖先,depxdep_xxx 到根路径上的边权和。

    • mm 次询问,每次询问给出 l,rl,r,求由 depLCA(i,j)(lijr)dep_{\text{LCA}(i,j)}\,(l \le i\le j\le r) 构成的集合中有几种本质不同的数。

    • n105n\le 10^5m5×105m\le 5\times 10^5

    • 500ms/512.00MB500 \,\text{ms}\,/\, 512.00 \,\text{MB}

    朴素的想法是把所有 depLCA(i,j)dep_{\text{LCA}(i,j)} 找出来然后做 2 - side\text{2 - side} 矩形数颜色,但是点对数量为 O(n2)\mathcal{O}(n^2) 个不可接受。不过所有的点对都有用吗?

    比如有四个点 a,b,c,da,b,c,d 满足 LCA(a,b)=LCA(c,d)=x\text{LCA}(a,b)=\text{LCA}(c,d) = xa<c<d<ba< c< d< b,这时我们发现 (a,b)(a,b) 这个点对是没用的。因为若一个区间包含了 (a,b)(a,b) 就一定包含了 (c,d)(c,d),而我需要统计 depxdep_x,保留 (c,d)(c,d) 就可以统计到。

    这时,我们称 (c,d)\boldsymbol{(c,d)} 支配了 (a,b)\boldsymbol{(a,b)}。称一个点对是支配点对当且仅当其没有被支配。容易发现,询问就是查询区间中支配点对的权值构成的集合中有多少种本质不同的数。

    考虑找到一个点对集合 SS 满足其包含所有支配点对且大小可以接受。容易发现我们此时需要寻找一个点对成为支配点对的必要条件。

    考虑在 LCA\text{LCA} 处统计。即,对于 x[1,n]x\in[1,n],当一个点对 (u,v)(u,v) 满足 LCA(u,v)=x\text{LCA}(u,v)=x 时,我们来寻找它成为支配点对的必要条件。

    首先,这两个点一定在 xx 的不同子树中。记 TT 表示 xx 的子树中 uu 所在子树外的点构成的集合,则 v\boldsymbol{v} 一定是 T\boldsymbol{T} 中最大的小于 u\boldsymbol{u} 的点(前驱 pre\boldsymbol{pre})或最小的大于 u\boldsymbol{u} 的点(后继 nxt\boldsymbol{nxt}

    以前驱为例,考虑反证,即当 vprev\ne pre 时,我们先理出已知条件:

    • v<pre<uv<pre<u

    • $v,pre\in T \Leftrightarrow \text{LCA}(v,u)=\text{LCA}(pre,u)=x$

    我们发现此时 (pre,u)(pre,u) 支配了 (v,u)(v,u)。后继证法类似,此处不再赘述。

    考虑一个树上启发式合并的过程,维护一个 std::set 表示当前子树及之前子树中所有点构成的点集,每次继承重儿子,遍历轻儿子时在 std::set 中找前驱、后继,并将该子树合并进 std::set

    那么对于一个支配点对 (u,v)(u,v),其一定满足上述条件,在启发式合并 LCA(u,v)\text{LCA}(u,v) 时,u,vu,v 一定在不同的子树中(或位于根)。不妨钦定 vv 所在子树更靠后,那么在处理 vv 所在子树之前,uu 已经被合并进了 std::set 中,且 uu 一定是 vv 的前驱 / 后继。那么对于 vv 找前驱 / 后继的时候就找到了这个点对。

    需要说清楚的是,对于 vv 而言,此时的 std::setT\boldsymbol{T} 的一个子集。不过一个点在 TT 中成为 vv 的前驱 / 后继的必要条件是它在这个自己中成为 vv 的前驱 / 后继,所以可以在子集中找前驱 / 后继。

    因此 TT 集合包含了所有的支配点对。并且,根据树上启发式合并的性质,一个点只会被遍历 O(logn)\boldsymbol{\mathcal{O}(\log n)} 次,一次遍历找的是前驱 / 后继,会带来 O(1)\boldsymbol{\mathcal{O}(1)} 个点对,因此 T=O(nlogn)\boldsymbol{|T|=\mathcal{O}(n\log n)},可以接受

    找出这些点对后,就可以进行 2 - side\text{2 - side} 矩形数颜色了。考虑扫描线,从右往左扫。设当前扫到 xx,维护一个数组 pospos 表示每种颜色在左点 x\ge x 的点对构成的点集中,右点的最小值。区间查询 [l,r][l,r] 等价于查询在左点 l\ge l 的点对构成的点集中,有多少种颜色的 posposr\le r,因此再对 pospos 维护一个权值树状数组即可。

    综上本做法时间复杂度为 O(nlog2n+qlogn)\mathcal{O}(n\log ^2 n+q\log n),空间复杂度为 O(nlogn+q)\mathcal{O}(n\log n+q)

    提交记录($\color{white}\colorbox{Limegreen} {\bf Accepted 100}$ 404ms/57.33MB\boldsymbol{404\,\bold{ms}\,/\, 57.33\,\bold{MB}} 代码

    • 1

    信息

    ID
    7213
    时间
    500ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者