题目大意
给出一棵
n
个点,根为
- 将
x 到根染成一种区别于之前所有颜色的颜色- 查询以 x 为根的子树的所有点到根的距离和
在这里距离的定义,假如两个相邻城市之间的颜色不同则距离为
1 。
初始状态下城市的颜色两两不同。n,Q<150,000
分析
因为初始状态下颜色互不相同,可以发现每个点到根的距离即它在树上的深度。
观察每一次将一个点到根染成同一种颜色后会发生什么变化。
倘若 y 是x 的父亲且它们都染成了一种新的颜色。- 若原本
y
与
x 的颜色不同,但此时却变为相同了,那么 x 的子树的所有点到根的距离将会减少1 。 - 若原本
y
与
x 的颜色相同,而现在依然是相同的,那么没有发生任何的变化。
Naive algorithm
一个很自然的想法就是每次修改操作沿途上跳,然后将按照上面的规则去修改子树中的所有点的权值。
时间复杂度 O(Qn)
空间复杂度 O(n)Idea 1
考虑用线段树按dfs序维护每个点到根的距离。那么子树的修改和查询就变成 O(logn) 的了。
查询时间复杂度 O(logn)
修改时间复杂度 O(nlogn)
空间复杂度 O(n)Idea 2
此时时间复杂度的瓶颈在于修改的时间复杂度。从上面的分析发现倘若 x 和
y 的颜色相同并没有发生任何的影响。但是我们修改时遍历了这些点,这无形中是一种浪费。我们可不可以把颜色相同的点并成一块呢?将这些点并成一块以后整棵树的样子就很有轻重链剖分的感觉了。实际上题目的操作是和link-cut tree里面的access操作是吻合的。那么剩下的就很显然了,我们仅仅需要在prefer child被切换时(也就是说链接一条重链时)去进行线段树的修改操作。
总的思路就是,用link-cut tree去维护这棵树。倘若两个点在同一条重链上那么它们的颜色相同。对于修改操作,则将这个点access,并在轻重链切换的时候修改线段树上的对应区间。对于询问操作,则是简单的线段树上区间查询。
至此这道题目就可以通过了。
时间复杂度 O(nlog2n)
空间复杂度 O(n)
后记
做这道题的初衷其实是因为它是一道动态树的题目而正好我在搞动态树的专题=v=。没想到它被收入到了集训队作业里了。
做这道题的时候有许多细节是要注意的
- prefer child 切换的时候修改线段树上的区间。修改哪个点的区间呢?应该是位于链顶的点对应的区间。也就是说我们需要实现一个 rightmost 函数来找到某棵 splay 中最大的元素。
- 各种TLE
- 不是任何时候加引用都可以加快的。比如传一个 int 没必要加引用。而传一个较大的结构体时加引用则有比较好的效果。
- 线段树的 push 不写成函数会快一点, update 同理。
- rightmost 函数找到最右的东西以后没必要旋上来。它可能会使得这棵树变丑。
- 线段树多写几个 if 而不是简单地判断需要的区间是否与当前区间重合的写法会较快。
- 为了减少link-cut tree里面的 top 函数调用次数,可以传入一个 bool 变量到 rotate 函数中来避免 rotate 里面的那个 top 的调用。
大概就是这样了。