前言
有时候,题目给我们的树的编号是没有规律的。
此时,我们需要变化节点的编号,才能完成一些询问。
比如,我们熟悉的 d f s dfs dfs序,它先将整棵树 d f s dfs dfs一遍,记录下每个节点的时间戳;我们熟悉的树剖,它用了一种特殊的方式遍历了整棵树,即,对于任何一个节点都先遍历它的重孩子。这两种算法的精髓在于,我们把一些编号不连续的需要被操作或询问的节点,转化为了一些编号连续的节点或分成了许多不同的段,其中每个段内都是编号连续的节点。
这篇文章, 将以上述算法精髓为起点,介绍一下其他的节点编号方法,希望能够给大家一些帮助!
bfs序
例题
给定一棵树,需要支持下面两种操作:
1 u d k 1\ u\ d\ k 1 u d k表示在以 u u u为根的子树中,所有深度为 d d d的点权全部加 k k k;
2 u d 2\ u\ d 2 u d表示在以 u u u为根的子树中,所有深度为 d d d的节点权值之和。
解法
读完本题后,可以发现 d f s dfs dfs序与树链剖分均不适用。我们考虑bfs序。
b f s bfs bfs序有一个重要的特点: 深度相同的一行的 b f s bfs bfs序连续。
于是,我们先对整棵树进行一次 b f s bfs bfs,对于一次形如 1 u d k 1\ u\ d\ k 1 u d k的操作,我们可以找到在以 u u u为根的子树中深度为 d d d的点的节点编号的左端点与右端点;此时我们操作或询问的是一段连续的区间,我们就可以使用线段树来维护啦。
但是,我们如何找到左端点 l l l与右端点 r r r呢?
显然, [ l , r ] [l,r] [l,r]区间内的数都是要被操作或询问的,其他的均不是。于是,我们直接二分查询 l l l与 r r r的值即可。更详细地解释一下求 l l l的值的步骤,我们对于该层所有节点进行二分,如果当前的 m i d mid mid的祖先中有 u u u,或 m i d mid mid的与 u u u深度相同的祖先在 u u u的右边,那么我们就向左找;否则向右找。求 r r r的方法同理。
注意,我们这里找 m i d mid mid的祖先中与 u u u同深度的祖先,可以使用预处理+倍增来求出。
综上所述,我们使用二分套倍增得到了 l , r l,r l,r的值,接着使用线段树进行修改或查询即可。
总时间复杂度为 O ( n l o g n + q l o g 2 n ) O(nlogn+qlog^2n) O(nlogn+qlog2n)。
中序
例题
给定一棵满二叉树。定义 s ( i ) s(i) s(i)表示以 i i i为根的子树中节点的数量。对于每个满足以 i i i为根的子树是对称二叉树的 i i i,求出 s ( i ) s(i) s(i)的最大值。
n ≤ 1 0 7 n≤10^7 n≤107
解法
如果我们直接深搜+减枝的话,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),无法通过本题。
可以发现,一棵二叉树对称,当且仅当这个二叉树的中序遍历是回文串。于是,我们可以找到原树的中序遍历,其中最长的一个回文串就是答案,显然可以用马拉车搞。
总时间复杂度 O ( n ) O(n) O(n)。