树中的乘法查询

Posted

技术标签:

【中文标题】树中的乘法查询【英文标题】:Multiplication queries in a tree 【发布时间】:2022-01-17 17:25:50 【问题描述】:

给定一棵包含 N 个节点 (1-N) 的树,每个节点都有一个初始值 A[i]。树的根是节点1

我们得到Q 类型的查询:

1 V X : multiply all nodes in subtree of `X` with value `V`.
2 X   : find the value of node `X`.  

约束:

N <= 10^5
Q <= 10^5

我的方法

说,我们有下面的树作为输入

       1
     /  \
    2    3
   /  \   \
   4  5    7
  /
 6

The idea is to traverse the tree using **DFS** and construct a tree 
traversal array that contains two values for each node.


index        : 0 1 2 3 4 5 6
node         : 1 2 4 6 5 3 7
subtree-size : 7 4 2 1 1 2 1

 and then we create a `segment tree` using above array.

             (0-6)
         /           \
      (0-3)           (4-6)
   /       \         /     \
  (0-1)   (2-3)     (4-5)  (6)
 /   \     /   \    /   \
(0) (1)  (2) (3)   (4)  (5)

--> So now when we get a query of type-1 say, 1 3 2. 
we need to multiply all nodes in 2's sub-tree, which means we need to do a update 
query at (1-4) in our segment tree. 
At each node of a segment tree we keep a initial multiplication factor as 1 and 
update it by multiplying it with the value 'V (in this case = 3)' whenever we get the query of type 1.
--> When we get query of type - 2, say 2 4 : we can now query our 
segment tree at (2, 2) and consolidate all the multiplication factors 
in the path and multiply it with the existing value of node '4' to return the
 result of the query.

使用这种方法,每个查询都可以使用时间O(log(n)) 来解决。我无法按时编写这种方法。

是否有任何其他更简单的方法来解决这个问题(可能不使用 seg-tree)并且根据约束查询时间应该至少与O(log(n))时间一样有效

【问题讨论】:

输入中树的边缘是如何编码的? @trincot,边以列表的形式提供给我们:[(u1,v1), (u2, v2)....] 它不一定是二叉树。 请一次问一个问题。添加语言标签,并以该语言显示您迄今为止所做的工作。 @MadPhysicist,正如我所说,我无法编写完整的方法,但我已经在问题中描述了我的方法。 您过去曾问过几个答案很好的问题,但没有答案被标记为已接受。这是有原因的吗? 【参考方案1】:

算法

首先执行一些预处理来构建树:

从边缘构建邻接列表 使用邻接表执行深度优先遍历,从根开始,识别每个节点的父节点。这可以存储在一个数组中,这样parents[i] 保存节点i 的父节点的索引,如果它存在,或者如果它没有父节点,则为-1。后者仅适用于根。 创建一个数组来存储每个节点的乘法因子,每个节点初始化为 1。

然后执行查询:

如果查询是类型 1,则将给定节点的已知因子乘以给定值。这需要固定的时间。 如果查询是类型 2,则累积从给定节点到根的路径上找到的因子(使用 parents 数组)。最后输出这个累积因子和给定节点的(原始)值的乘积。这具有 O(logn) 平均时间复杂度。

示例

这是一个示例输入(您确实应该提供!),但它是基于 0 索引的,因为这在编程语言中更为常见。所以根节点用索引0标识:

values: [2, 1, 4, 9, 3, 5]
edges: [[0, 1], [0, 2], [1, 3], [1, 4], [2, 5]]
queries: [
    [2, 2],
    [1, 3, 2],
    [2, 5],
    [1, 10, 0],
    [2, 5],
    [2, 1],
]

这表示以下树:

               2
             /   \
            1     4
           / \   /
          9   3 5

当所有查询都执行完毕后,树可以这样表示,其中每个节点都伴随着一个因子:

               2(10)
             /   \
            1     4(3)
           / \   /
          9   3 5

查询的预期输出是:

4
15
150
10

实施

以下是 javascript 中的示例实现。同样,这是使用基于 0 的索引。它运行上面的示例输入。

// Helper function for building the adjacency list
function dfs(adj, parents, node) 
    for (let child of adj[node]) 
        if (parents[child] == -2)  // Not yet visited
            parents[child] = node;
            dfs(adj, parents, child);
        
    


// Main function to create the adjacency list
function prepare(values, edges) 
    let adj = values.map(value => []); // For each node an empty list (vector)
    
    // Populate the adjacency lists from the given edges
    for (let [u, v] of edges) 
        // Register edge in two directions
        adj[u].push(v);
        adj[v].push(u);
    
    
    let parents = Array(values.length).fill(-2); // Parents are unknown
    parents[0] = -1; // It is a given that the root is at 0: it has no parent
    dfs(adj, parents, 0); // Identify all other parents
    return parents;


// Two functions for performing two types of queries
function multiply(values, parents, factors, factor, i) 
    factors[i] *= factor;


function calculate(values, parents, factors, i) 
    let factor = 1; 
    // Walk up the tree to accumulate the applicable factors
    for (let j = i; j > -1; j = parents[j]) 
        factor *= factors[j];
    
    return values[i] * factor;


function solve(values, edges, queries) 
     // Preprocessing:
    let parents = prepare(values, edges);
    let factors = Array(values.length).fill(1); // All factors start as 1
    // Execute the queries:
    for (let query of queries) 
        if (query[0] == 1) 
            multiply(values, parents, factors, query[1], query[2]);
         else 
            let value = calculate(values, parents, factors, query[1]);
            console.log(value);
        
    


// Example input:
let values = [2, 1, 4, 9, 3, 5];
let edges = [[0, 1], [0, 2], [1, 3], [1, 4], [2, 5]];
let queries = [
    [2, 2],  // => 4
    [1, 3, 2],
    [2, 5], // => 15 (=5*3)
    [1, 10, 0],
    [2, 5], // => 150 (=5*3*10)
    [2, 1], // => 10 (1*10)
];

solve(values, edges, queries);

【讨论】:

感谢您的解决方案,但是我不明白您的解决方案中查询 2 的时间复杂度是 O(log(n)),假设我们有一个倾斜的树,作为输入,我们得到第二种类型的查询 = [0, 1, 2, 3......n-1],所以对于每个查询,我们一直到它的父级检查乘法父级,这似乎 O(n ^2) 给我。您能否解释一下每个查询的平均时间复杂度如何在您的解决方案中保持 O(log(n))。 当然,如果树完全倾斜,那么一次查询将花费 O(n),其中 n 是树中的节点数。这就是为什么我将其称为 平均 时间复杂度,即您对随机树的期望。如果树只是随机的,那么树的预期高度为 O(logn),其中 n 是节点数。因此,当我写“平均”时,我指的不是同一树上查询的平均值,而是所有可能树上的平均值。 好的,最坏情况下的时间复杂度可以达到 O(log(n)) 吗? 我不明白。 好的,所以使用类似于 DS 的分段树,它保持在 O(log(n)) 高度,因此可以在 O(log(n)) 时间内回答我们的查询只能是解决方案。跨度>

以上是关于树中的乘法查询的主要内容,如果未能解决你的问题,请参考以下文章

java中如何查询下面树中的的所有用户?只知道根部门的ID,如何遍历所有用户

将不同树中的两个表加入 phpmyadmin

需要避免 join 中的乘法 sum()

如何最好地存储kd树中的行

校招的常考算法类型以及对应的典型题目

谈谈 InnoDB引擎中的一些索引策略