简体   繁体   English

树中路径的范围查询

[英]Range Queries on a path in a tree

I came across this question in a contest (which is now over) and I am not able to think of a time-efficient algorithm.我在一场比赛中遇到了这个问题(现在已经结束),我想不出一个省时的算法。

You are given a rooted Tree of N ( <=10^5) nodes .您将获得 N ( <=10^5) 个节点的有根树。 Initially all nodes have value 0.There would be M updates (<=10^5) to the tree which are of the form最初所有节点的值都为 0。树会有 M 次更新(<=10^5),其形式如下

Add xy – Add y to node x .添加 xy – 将 y 添加到节点 x 。

AddUp xy – Add y to x , parent of x , parent of parent of x uptill Root. AddUp xy – 将 y 添加到 x , x 的父级, x 的父级的父级直到 Root。

After that there will be Q queries ( <=10^5) queries where you will either be asked to give the value of a node or the sum of subtree rooted at that node.之后将有 Q 个查询 (<=10^5) 个查询,您将被要求提供节点的值或以该节点为根的子树的总和。

What I did:-我做了什么:-

First I tried the naive algorithm that would update each node according to the operation, but obviously it is time taking.首先我尝试了根据操作更新每个节点的朴素算法,但显然这很耗时。

I also thought of using segment trees and Lazy propogation but cannot think of a proper way.我也想过使用段树和延迟传播,但想不出合适的方法。

Any help is appreciated , Thanks!任何帮助表示赞赏,谢谢!

First, construct a graph where the children point to their parents.首先,构建一个图,其中孩子指向他们的父母。 After that, parse all the updates and store in each node of your tree the sum of Add and AddUp separately.之后,解析所有更新并将 Add 和 AddUp 的总和分别存储在树的每个节点中。 Your node should have the following variables:您的节点应具有以下变量:

sum_add : the sum of all the Add of this node
sum_add_up : the sum of all the AddUp of this node
subtree_sum : the sum of the subtree. Initialize with 0 by now.

Now, transverse your graph using topological order, ie, you will only process a node if all of its children were already processed, which takes O(N).现在,使用拓扑顺序横向您的图,即,如果它的所有子节点都已被处理,您将只处理一个节点,这需要 O(N)。 Let me now define the process function.现在让我定义流程函数。

process(V) {
    V.sum_add_up = V.sum_add_up + sum(sum_add_up of all V.children)
    V.subtree_sum = V.sum_add + V.sum_add_up + sum(subtree_sum of all V.children)
}

Now you can answer all the queries in O(1).现在您可以回答 O(1) 中的所有查询。 The query for the value of a node V is V.sum_add + V.sum_add_up , and the query for the subtree of V is just V.subtree_sum .查询节点V的值是V.sum_add + V.sum_add_up ,查询V的子树就是V.subtree_sum

This is a Fenwick Tree, for solve this kind of problems you must to execute a topological sort on tree and count the number of childrens for each node.这是一棵 Fenwick 树,要解决此类问题,您必须对树执行拓扑排序并计算每个节点的子节点数。

      0
    /   \
   1     2
  / \
 3   4

index: [0 1,2,3,4] childrens: [4,2,0,0,0] With topological you will obtain this vector 0 1 3 4 2 you need to reverse it:索引:[0 1,2,3,4] 孩子:[4,2,0,0,0] 使用拓扑,您将获得此向量 0 1 3 4 2 您需要将其反转:

fenwick Pos:  [0,1,2,3,4]
vector values:[2,4,3,1,0]
pos: [5,3,0,2,1]

With fenwick tree you can execute 2 kinds of query, update query, range sum query when you need to update a only a index call update(pos[index], y) , then you must decrease all next values, update(pos[index]+1, -y) When you need to update all parents call update(pos[index], y) and update(pos[index] + childrens[index] + 1, -y);使用 fenwick 树,您可以执行 2 种查询,更新查询,范围总和查询,当您只需要更新一个索引调用update(pos[index], y) ,然后您必须减少所有下一个值, update(pos[index]+1, -y)当需要更新所有父级时调用update(pos[index], y)update(pos[index] + childrens[index] + 1, -y);

for know value of a position you need to call range sum query on pos[index]要知道位置的值,您需要在 pos[index] 上调用范围总和查询

I think this problem is just a direct application of a Binary Search Tree, which has an average-case cost (after n random operations) O(1.39log(n)) for both inserts and queries.我认为这个问题只是二叉搜索树的直接应用,对于插入和查询,它的平均成本(在 n 次随机操作之后) O(1.39log(n))

All you have to do is recursively add new nodes and update values and sum at the same time.您所要做的就是递归地添加新节点并同时更新值和求和。

Implementation is also fairly simple (sorry for C#), for example for Add() ( AddUp() is similar - increase value every time you go to left or right subtree):实现也相当简单(对不起 C#),例如Add()AddUp()类似 - 每次转到左子树或右子树时增加值):

public void Add(int key, int value)
{
    Root = Add(Root, key, value);
}

private Node Add(Node node, int key, int value)
{
    if (node == null)
    {
        node = new Node(key, value, value);
    }

    if (key < node.Key)
    {
        node.Left = Add(node.Left, key, value);
    }
    else if (key > node.Key)
    {
        node.Right = Add(node.Right, key, value);
    }
    else
    {
        node.Value = value;
    }

    node.Sum = Sum(node.Left) + Sum(node.Right) + node.Value;

    return node;
}

For 100000 numbers on my machine this translates to these numbers:对于我机器上的 100000 个数字,这将转换为这些数字:

Added(up) 100000 values in: 213 ms, 831985 ticks
Got 100000 values in:       86 ms, 337072 ticks

And for 1 million numbers:对于 100 万个数字:

Added(up) 1000000 values in: 3127 ms, 12211606 ticks
Got 1000000 values in:       1244 ms, 4857733 ticks

Is this time efficient enough?这个时间够效率吗? You can try complete code here .您可以在此处尝试完整代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM