题解 CF1528C 【Trees of Tranquillity】

iMya_nlgau

2021-05-26 21:18:16

题解

题目大意

给你两棵 n 个节点的树,要你找一个最大的点集 S,使得对于任意 u,v\in S,都满足 u,v 在第一棵树上一个是另一个的祖先,在第二棵树上互相都没有祖先关系,输出 S 的大小即可。

思路

我们可以 dfs 第一棵树,搜到节点 x 的时候维护 x 到根节点的路径上的点(它们在第一棵树上一定满足两两有祖先关系)。现在要做的就是在第二棵树上维护在这些点最多选多少点两两没有祖先关系。

我们可以维护当前这些点在第二棵树上构成的虚树(只是这么说,并不是真的要建虚树)。容易发现,虚树的叶子节点就是能选出的满足条件的最大点集。

于是可以树剖维护第二棵树,每加入一个点 x,如果 x 没被标记,就把 x 的子树以及 x 到根节点路径都打上标记(比如异或上 x),如果标记过了,若标记的点是 x 的祖先,删掉它并加入 x,如果 x 是标记的点的祖先,那就不加入 x

dfs 第一棵树时,还要在搜完一个点的时候回溯,所以记录下所有操作,回溯时在树剖上把操作撤销。

这个做法是我赛后想到的(wtcl 赛时没做出来)。时间复杂度为 O(n\log^2 n),应该能过。

后来看了官方题解的做法,它用 set 维护当前答案集合里点的欧拉序,做的更简单些。

欧拉序(对每个点记录一个进入的时间戳 st,和离开的时间戳 ed)有点像一个括号序列,即每个区间 [st_i,ed_i] 都要么是包含关系,要么不相交。我们要做的就是维护当前最多多少个区间两两不相交,这个可以用 set 维护。采用一个贪心的思路,加入一个区间时,如果它被其他区间包含,那么把包含它的区间换成它一定不劣,如果新加入的区间包含了其他区间,那么加入它一定不优,不加入它,如果新加入的区间不与其他区间相交,直接加入即可。

依然是在 dfs 第一棵树的时候维护上述东西,搜完一个点时回溯。这个做法的时间复杂度为 O(n\log n)

代码实现(set)