(搬运)如何更快地遍历无根树

xzf_200906

2024-12-28 07:23:58

Algo. & Theory

声明:本篇文章搬运自 CF 上的一篇文章。在搬运时,本人已经取得了原作者的同意。

Upd on 12.31:修正了一处错误和一处表意不清。

引入

在许多问题中会给定一棵树的所有边,要求指定一个节点为根并且得知每个节点的父节点。此时通常会使用 DFS 进行计算。但我们希望其可以更快以抢到某题的最优解或冲过毒瘤题

算法设计

由于不管使用何种数据结构存边都会导致较大的常数,我们考虑不再存储每一条边,而是存储每个节点 i 的度数 deg_i 以及相邻节点的编号异或和 xor_i。事实上,这些信息足够让我们还原整棵树。考虑从小到大枚举每个点 i,并令 p=i。执行以下步骤:

不难发现在做完上述操作后所有的 deg_p=0,则整棵树被删空。此时存储的 xor 数组为整棵树以某个点为根时的每个父亲结点。

事实上,这个根节点是可以被指定的。只需要将它的 deg 设为 0 即可保证其不会被访问。不难发现算法结束后除了它之外的所有节点的 deg0,则我们得到了以该节点为根时所有节点的父亲。由于该算法仅涉及到了加减法以及异或操作,故常数较小。

如何得到 dfs 序

注意,此处的算法与文章中提到的不同。

我们设 dfn_p 表示该点在 dfs 序中的排名, siz_p 表示子树大小,dif_p 表示 p 与其父亲 dfn 的差值(即在遍历完 p 的父亲后再遍历包括点 p 在内的多少个点可以到达点 p)。特别地,对于根节点 r,有 dif_r=1。则在上述算法删去点 p 前额外按顺序做以下操作:

对于任意点 p,其所有祖先的 dif 之和即为其 dfn。若按上述遍历顺序的倒序遍历所有节点,则每个节点的父亲都会先于该节点被遍历到,则 dfn 不难计算。

注意,该算法不仅可以应用在此处,如果输入中直接给出了每个节点的父亲也可以使用此算法。

对边的处理

在某些问题上,我们不仅希望得到每个节点的父亲,还希望得到其连向父亲的边。类比上述的处理方式,可以设一个变量表示与某个点相邻的所有边的编号异或和,用与上面类似的方式维护即可。如果边上的信息是一个整数,则同样可以记录边权的异或和。

在 [NOI2011] 道路修建 中,本算法在除了使用快读快写外没有刻意卡常的情况下,目前(2024.12.27)为最优解,且运行时间是第二名的一半。