我们获得了一个可以使用的定向树。我们定义 p-ancestor 和 p-cousin 的概念如下
p-ancestor:如果节点是其父节点,则节点是另一节点的 1-ancestor 。它是节点的 p-ancestor ,如果它是(p-1)-th ancestor 的父节点。
p-cousin:如果节点共享相同的 p-ancestor ,则该节点是另一个节点的 p-cousin 。
例如,请考虑下面的树。
4有三个1-cousins i,e,3,4和5,因为它们都有共同之处 1祖先,即1
对于特定树,问题如下。您将获得多对(node, p ),并且应该计算(并输出)相应节点的 p-cousin 的数量。
慢速算法是爬行到 p-ancestor 并为每个节点运行BFS。
什么是(渐近)解决问题的最快方法?
答案 0 :(得分:1)
如果离线解决方案可以接受,则两次深度优先搜索可以完成这项工作。
假设我们可以将所有n
次查询(node, p)
从0索引到n - 1
我们可以将每个查询(node, p)
转换为另一种类型的查询(ancestor , p)
,如下所示:
回答查询(node, p)
,节点的级别为a
(从根节点到此节点的距离为a
),是祖先级别a
的级别数在a - p
级。因此,对于每个查询,我们都可以找到谁是祖先:
伪代码
dfs(int node, int level, int[]path, int[] ancestorForQuery, List<Query>[]data){
path[level] = node;
visit all child node;
for(Query query : data[node])
if(query.p <= level)
ancestorForQuery[query.index] = path[level - p];
}
现在,在第一个DFS之后,我们有一种新类型的查询(ancestor, p)
假设我们有一个数组count
,它在索引i
存储具有级别i
的节点数。假设a
级别的节点x
,我们需要计算p
个后代的数量,因此,此查询的结果是:
query result = count[x + p] after we visit a - count[x + p] before we visit a
伪代码
dfs2(int node, int level, int[] result, int[]count, List<TransformedQuery>[]data){
count[level] ++;
for(TransformedQuery query : data[node]){
result[query.index] -= count[level + query.p];
}
visit all child node;
for(TransformedQuery query : data[node]){
result[query.index] += count[level + query.p];
}
}
每个查询的结果都存储在result
数组中。
答案 1 :(得分:0)
如果p是固定的,我建议采用以下算法:
假设count [v]是v的p子数。最初所有count [v]都设置为0.而pparent [v]是v的p-parent。
现在让我们在树上运行dfs并保留被访问节点的堆栈,即当我们访问某些v时,我们将它放入堆栈中。一旦我们离开v,我们就会弹出。
假设我们在dfs中遇到了一些节点v。让我们计算[stack [size - p]] ++,表明我们是v的p子。还有pparent [v] = stack [size-p]
一旦你的dfs完成,你可以像这样计算所需的p-cousins数: 算[pparent [V]]
这对于dfs是O(n + m),对于每个查询是O(1)
答案 2 :(得分:0)
首先,我将描述一种在O(p)时间内使用O(n)预处理时间和空间回答每个查询的相当简单的方法,然后提一种查询时间可以加速到O的方式(log p )时间因素只是O(log n)额外的预处理时间和空间。
基本思想是,如果我们写出在树的DFS遍历期间访问的节点序列,使得每个节点都在与树中的级别相对应的垂直位置写出,那么节点的p-cousins在此图中形成水平间隔。请注意,这种“书写”看起来非常像典型的树形图,除非没有连接节点的行,并且(如果使用后序遍历;预订顺序也一样好)父节点总是出现在他们孩子的右边。因此,给定一个查询(v,p),我们将要做的主要是:
预处理步骤是我们为每个1 <= i <= n计算x [i]的地方。这是通过执行构建第二个数组y []的DFS来实现的,该数据y []记录到目前为止在深度d访问的节点的数量y [d]。具体而言,y [d]对于每个d最初为0;在DFS期间,当我们访问深度为d的节点v时,我们只需递增y [d]然后设置x [v] = y [d]。
如果树相当平衡,上述算法应该已经足够快 - 但在最坏的情况下,当每个节点只有一个子节点时,O(p)= O(n)。请注意,它在上述4个步骤中的前3个步骤中在树中上下导航,强制执行O(p)时间 - 最后一步需要恒定时间。
要解决这个问题,我们可以添加一些额外的指针,以便更快地在树上上下导航。一种简单而灵活的方法使用“指针加倍”:对于每个节点v,我们将log2(depth(v))指针存储到连续更高的祖先。为了填充这些指针,我们执行log2(maxDepth)DFS迭代,其中在第i次迭代中,我们将每个节点v的第i个祖先指针设置为其第(i-1)个祖先的第(i-1)个祖先:每个DFS每个节点只需要两次指针查找。使用这些指针,向上移动任何距离p总是最多需要log(p)跳跃,因为每次跳跃的距离可以减少至少一半。可以使用完全相同的过程来填充“左后代”和“右后代”的相应指针列表,以分别将步骤2和3加速到O(log p)时间。