更新给定范围的数组值

时间:2015-07-04 11:21:41

标签: java algorithm segment-tree

给出了一个最初具有一些Max.val值的数组,然后有一些查询在Range L,R中进行更新,这样任何位置的值都是最小的。例如:

Update Range 1 3 with value 3 
Array  3 3 3 Max.val Max.val
Now update 2 and 4 with 1
Array  3 1 1 1 Max.val
Now update 1 and 2 with 2
Array  2 1 1 1 Max.val
i.e update only if(A[i]>value) A[i]=value;

在上述查询之后,我必须显示我的最终array:i.e 2 1 1 1 Max.val

我正在使用细分树来解决此问题,但我收到了TLE(超出时间限制)。我不知道为什么? 我的方法是logN。 这是我的更新功能

public static void lets_change(int curr, int[] T, int x, int y, int a, int b, int c) {
    // x=0 and y=n and a=L , b=R and C= Value to be updated
    // T contains Integer.Max_value at start
    if (x > y || b < x || a > y)
        return;

    if (x == y) {
        T[curr] = Math.min(T[curr], c);
        return;
    }
    lets_change(2 * curr, T, x, (x + y) / 2, a, b, c);
    lets_change(2 * curr + 1, T, (x + y) / 2 + 1, y, a, b, c);

    T[curr] = Math.min(T[2 * curr], T[2 * curr + 1]);
}

约束:

N<=10^5;
Q<=10^5;
1<L<=R<=10^5

我做错了什么或有更好的方法? 调用函数:

for(int i=0;i<Q;i++){
    int l = in.nextInt()-1;
    int r = in.nextInt()-1;
    int c = in.nextInt();
    lets_change(1,T,0,n-1,l, r,c);
}

4 个答案:

答案 0 :(得分:3)

你的方法不是O(log n),因为从L更新到R你至少要更新L和R之间的所有位置(T[curr] = Math.min(T[curr], c))。

要真正实现O(log n)更新,您必须实现segment tree with lazy propagation。结构的要点是避免更新每个位置。每当面对覆盖整个范围的递归更新时,请不要立即更新,只需标记范围节点以便稍后更新。在更新(或查询)时,在需要时传播计划的更新(当查询或更新仅覆盖范围的一部分并且您需要更深入时)。

答案 1 :(得分:1)

您可以在O(qlog(n))中执行此操作,其中q是查询数。

想法是使用BIT(二进制索引树)

// C++ program to demonstrate Range Update
// and Range Queries using BIT
#include <iostream>
using namespace std;

// Returns sum of arr[0..index]. This function assumes
// that the array is preprocessed and partial sums of
// array elements are stored in BITree[]
int getSum(int BITree[], int index)
{
    int sum = 0; // Initialize result

    // index in BITree[] is 1 more than the index in arr[]
    index = index + 1;

    // Traverse ancestors of BITree[index]
    while (index>0)
    {
        // Add current element of BITree to sum
        sum += BITree[index];

        // Move index to parent node in getSum View
        index -= index & (-index);
    }
    return sum;
}

// Updates a node in Binary Index Tree (BITree) at given
// index in BITree.  The given value 'val' is added to
// BITree[i] and all of its ancestors in tree.
void updateBIT(int BITree[], int n, int index, int val)
{
    // index in BITree[] is 1 more than the index in arr[]
    index = index + 1;

    // Traverse all ancestors and add 'val'
    while (index <= n)
    {
        // Add 'val' to current node of BI Tree
        BITree[index] += val;

        // Update index to that of parent in update View
        index += index & (-index);
    }
}

// Returns the sum of array from [0, x]
int sum(int x, int BITTree1[], int BITTree2[])
{
    return (getSum(BITTree1, x) * x) - getSum(BITTree2, x);
}


void updateRange(int BITTree1[], int BITTree2[], int n,
                 int val, int l, int r)
{
    // Update Both the Binary Index Trees
    // As discussed in the article

    // Update BIT1
    updateBIT(BITTree1,n,l,val);
    updateBIT(BITTree1,n,r+1,-val);

    // Update BIT2
    updateBIT(BITTree2,n,l,val*(l-1));
    updateBIT(BITTree2,n,r+1,-val*r);
}

int rangeSum(int l, int r, int BITTree1[], int BITTree2[])
{
    // Find sum from [0,r] then subtract sum
    // from [0,l-1] in order to find sum from
    // [l,r]
    return sum(r, BITTree1, BITTree2) -
           sum(l-1, BITTree1, BITTree2);
}


int *constructBITree(int n)
{
    // Create and initialize BITree[] as 0
    int *BITree = new int[n+1];
    for (int i=1; i<=n; i++)
        BITree[i] = 0;

    return BITree;
}

// Driver Program to test above function
int main()
{
    int n = 5;

    // Construct two BIT
    int *BITTree1, *BITTree2;

    // BIT1 to get element at any index
    // in the array
    BITTree1 = constructBITree(n);

    // BIT 2 maintains the extra term
    // which needs to be subtracted
    BITTree2 = constructBITree(n);

    // Add 5 to all the elements from [0,4]
    int l = 0 , r = 4 , val = 5;
    updateRange(BITTree1,BITTree2,n,val,l,r);

    // Add 2 to all the elements from [2,4]
    l = 2 , r = 4 , val = 10;
    updateRange(BITTree1,BITTree2,n,val,l,r);

    // Find sum of all the elements from
    // [1,4]
    l = 1 , r = 4;
    cout << "Sum of elements from [" << l
         << "," << r << "] is ";
    cout << rangeSum(l,r,BITTree1,BITTree2) << "\n";

    return 0;
}

高效的解决方案是确保两个查询都可以在O(Log n)时间内完成。我们使用前缀和得到范围和。如何确保以某种方式完成更新,以便快速完成前缀总和?考虑在范围[1,r]上的范围更新之后需要前缀sum [0,k](其中0 <= k

案例1:0&lt; k&lt;升 更新查询不会影响求和查询。

情况2:l&lt; = k&lt; = r 考虑一个例子:

将2添加到范围[2,4],结果数组将为: 0 0 2 2 2 如果k = 3 从[0,k] = 4求和 如何得到这个结果? 只需将第l个索引的val添加到第k个索引。在更新查询之后,Sum增加“val *(k) - val *(l-1)”。

案例3:k> [R 对于这种情况,我们需要从第l个索引添加“val”到rth索引。由于更新查询,Sum增加“val r - val (l-1)”。

观察: 案例1:很简单,因为总和将保持与更新前相同。

情况2:Sum增加了val k - val (l-1)。我们可以找到“val”,它类似于在范围更新和点查询文章中查找第i个元素。所以我们为Range Update和Point Queries维护一个BIT,这个BIT将有助于在第k个索引处找到值。现在计算val * k,如何处理额外的术语val *(l-1)? 为了处理这个额外的期限,我们维持另一个BIT(BIT2)。在第l个索引处更新val *(l-1),因此当在BIT2上执行getSum查询时,将得到结果为val *(l-1)。

情况3:情况3中的总和增加“val * r - val (l-1)”,该项的值可以使用BIT2获得。而不是添加,我们减去“val (l-1) - val r”,因为我们可以通过添加val (l-1)从BIT2获取此值,就像我们在案例2中所做的那样并在每次更新操作中减去val * r。

Update Query 
Update(BITree1, l, val)
Update(BITree1, r+1, -val)
UpdateBIT2(BITree2, l, val*(l-1))
UpdateBIT2(BITree2, r+1, -val*r)

Range Sum 
getSum(BITTree1, k) *k) - getSum(BITTree2, k)

资料来源:geeksforgeeks.org

答案 2 :(得分:0)

void lets_change(int curr, int T[] , int x, int y, int a, int b, int c) {

       if(lazy[curr] != inf ) { // This node needs to be updated
        T[curr] =min(T[curr], lazy[curr]); // Update it

        if(x != y) {
            lazy[curr*2] = min(lazy[2*curr],lazy[curr]); // Mark child as lazy
                lazy[curr*2+1] = min(lazy[2*curr+1],lazy[curr]); // Mark child as lazy
        }

        lazy[curr] = inf; // Reset it
    }

    if (x > y || b < x || a > y)
        return;

    if (x == y) {
        T[curr] = min(T[curr], c);
        return;
    }
    lets_change(2 * curr, T, x, (x + y) / 2, a, b, c);
    lets_change(2 * curr + 1, T, (x + y) / 2 + 1, y, a, b, c);

    T[curr] = min(T[2 * curr], T[2 * curr + 1]);

}

这是我的懒惰版本,但它是用c ++编写的。将懒惰部分添加到查询功能中,您将很高兴。如果您需要访问整个阵列,则可能需要重新设计查询功能。

答案 3 :(得分:0)

//Modifying an element is also quite straightforward and takes time proportional to the height of the tree, which is O(log(n)).** 
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5;
    int t[2*N];
    int n;
    void build()//build the tree 
    {
        for(int i=n-1;i>0;i--)
        {
            t[i]=t[i<<1]+t[i<<1|1];
        }
    }
    void query(int l,int r)
    {
        int res=0;
        for(l+=n,r+=n;l<r;l>>=1,r>>=1)
        {
            if(l&1)res+=t[l++];
            if(r&1)res+=t[--r];
        }
        cout<<res;
    }

    void modify(int p,int value)//updating the value
    {
        for(t[p+=n]=value; p>1 ; p>>=1)
        {
            t[p>>1]=t[p]+t[p^1];
        }
    }

    int main()
    {
        int r1,r2;
        cin>>n;
        for(int i=0;i<n;i++)
        {
           cin>>t[i+n];
        }
        build();
        cin>>r1>>r2
        modify(r1,r2);
        query(0,4);
        }
    }

现在,让我们看看为什么这样做有效并且非常有效。

从图片中您可以注意到,叶子存储在以n开头的连续节点中,索引为i的元素对应于索引为i + n的节点。因此我们可以将初始值直接读入它们所属的树中。

在执行任何查询之前,我们需要构建树,这很简单,并且花费O(n)的时间。由于父级的索引始终小于子级,因此我们仅按降序处理所有内部节点。如果您对位操作感到困惑,则build()中的代码等效于t [i] = t [2 * i] + t [2 * i + 1]。

修改元素也非常简单,并且花费的时间与树的高度成正比,即O(log(n))。我们只需要更新给定节点的父节点中的值即可。这样我们就知道了节点p的父节点是p / 2或p >> 1,这意味着它是相同的。 p ^ 1将2 * i变成2 * i + 1,反之亦然,因此它代表p的父级的第二个孩子。

一般思路如下。如果l(左间隔边界)是奇数(等于l&1),则l是其父级的右子级。然后,我们的间隔包括节点l,但不包括其父节点。因此,我们将t [l]加起来,并通过设置l =(l + 1)/ l2来移至l的父级的右边。如果l是偶数,则它是左子级,并且区间也包括其父级(除非右边边界会干扰),因此我们只需通过设置l = l / 2移至该位置即可。边界一遇,我们就停下来。

不进行递归操作,也不需要进行其他计算(例如找到间隔的中间部分),我们只需遍历所有需要的节点,因此非常高效。