loj2474《北校门外的未来》题解

鉴于lca本人已经写过此题命题报告,所以可以认为本文是命题报告的注解以及实现指南。
好神仙的一道题,前前后后总共耗了我差不多三整天时间。
最开始看到这题是集训队互测loj上同步赛的现场,当时就觉得很吸引人。
然而并不会做,膜了一波sol,然而还是不会。
后来ZJOI2018R2第二天Scape讲题时又提到了此题,还是不会。
暑假里尝试过一波,并没有什么进展。
现在强推了一波,终于干掉了此题。

题解部分自此开始。
很多LCA命题报告里有的东西不会再赘述。
所以假定阅读此文的读者已经仔细阅读过LCA的命题报告,对原文中的各个定理已经很熟悉了。
符号尽量会与原论文统一。
我的代码在此
想要解决此题,首先要引入笛卡尔树的概念,子任务2对此有大致的介绍。
然后就是解决静态问题,可以看子任务3。
子任务4到7并不是解决本题的必读部分,可以直接跳到子任务8,9的部分。
首先,原文中有一句”笛卡尔树可以离线维护来降低常数“,先介绍一下我用到的静态操作,以便后文使用。

void ini()

用于建立静态的笛卡尔树,可以按编号从小到大来建立,使用并查集来维护只考虑前i个点构成的“笛卡尔森林”中某个点所在树的根。

int lca(int x,int y)

用于查询当前的笛卡尔树中x和y的lca。

int getv(int x,int y)

用于查询原树中x到y的路径上离x最近的那个点。
如果x是y的祖先,那么就在x的儿子里二分y的dfn序,否则返回x的爸爸。

int getson(int u,int v)

用于查找当前笛卡尔树中从u往上爬到v之前的最后一个点。
对于这个问题,我是使用重链剖分,从u往上到v的 O ( l o g n ) O(logn) O(logn)条重链里,对于不包括点v本身的重链,直接用该重链里的最浅点来更新。

bool cke(int x,int y)

其中u是v在笛卡尔树上的祖先。
用于判定y能否一步跳到x。
一方面是回答询问的需要,另一方面,实现了这个函数,可以辅助debug。

然后考虑如何维护 F ( T ) F(T) F(T)
如果是静态问题,原报告中已经有过介绍,其中“每个点对应的底端”应该是指某个点b到 f C ( T ) ( b ) f_{C(T)}(b) fC(T)(b)在原树中的路径上,离 f C ( T ) ( b ) f_{C(T)}(b) fC(T)(b)最近的一个点。
如果是动态加叶子,对于加叶子操作(u,v),原文中并没有提到 f F ( T ) ( v ) f_{F(T)}(v) fF(T)(v)应该如何维护,实际上 f F ( T ) ( v ) = f F ( T ) ( u ) f_{F(T)}(v)=f_{F(T)}(u) fF(T)(v)=fF(T)(u)(2020.03.17upd:经指出该等式右边应为 f F ( T ) ( g e t s o n ( v , u ) ) f_{F(T)}(getson(v,u)) fF(T)(getson(v,u))),证明就是注意到在 C ( T ) C(T) C(T)上一个点到根的路径构成一个有序序列。
关于处理加入v后对其它点在 F ( T ) F(T) F(T)里的父亲的影响,先要建立一个类似于将 F ( T ) F(T) F(T)三度化的 G ( T ) G(T) G(T),跟配对堆有点像,原文介绍过 G ( T ) G(T) G(T)的定义。
然后要处理v对其它点的影响,就是在 G ( T ) G(T) G(T)里找到u往上的第一条权为一的边,整条链切下来接到v下面。然后u爬到这条链顶在 ( C ( T ) (C(T) (C(T)上的爸爸,继续前述操作,直到链顶大于v。
但是直接切下来一条链是有问题的,除了链顶的边权会1变0,还可能有别的边0变1,这一点是原文所没提到的。
想要解决这个问题,可以先发现一条性质:每个点在 G ( T ) G(T) G(T)里往下最多有一条权为0的边。
现在的目标就是找出这条边。
记该点为x,则我们所求的点就是 f C ( T ) ( x ) f_{C(T)}(x) fC(T)(x)和x在原树中所“夹”出来的连通块中的最大值。
用前述静态操作来说,就是getson(getv( x , f C ( T ) ( x ) f_{C(T)}(x) fC(T)(x) ),x)。

最后考虑查询。
对于询问(x,y),我们需要先找出lca(x,y),然后把询问拆成query(x,lca)和query(y->lca)。
其中query要返回两个值,一是爬到lca前的最后一个点,二是爬到这个点的步数。

你可能感兴趣的:(loj2474《北校门外的未来》题解)