5

已经给出了一个最初具有一些 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

我正在使用Segment Tree来解决这个问题,但我得到了 TLE(Time limit Exceeded)。我不知道为什么?我的方法是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

4 回答 4

3

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

要真正实现 O(log n) 更新,您必须使用延迟传播实现分段树。该结构的要点是避免更新每个位置。每当遇到覆盖整个范围的递归更新时,不要立即更新,只需将范围节点标记为稍后更新即可。并且在更新(或查询)时,在需要时传播计划的更新(当查询或更新仅覆盖范围的一部分并且您需要更深入时)。

于 2015-07-04T11:42:30.217 回答
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) 时间内完成。我们使用前缀总和得到范围总和。如何确保更新以某种方式完成,以便可以快速完成前缀和?考虑在范围 [l, r] 上进行范围更新后需要前缀和 [0, k](其中 0 <= k < n)的情况。出现三种情况,因为 k 可能位于 3 个区域中。

情况一:0 < k < l 更新查询不会影响求和查询。

情况 2:l <= k <= r 考虑一个例子:

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

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

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

情况 2:Sum 增加了 val k – val (l-1)。我们可以找到“val”,它类似于在范围更新和点查询文章中找到第i个元素。所以我们为范围更新和点查询维护一个 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

于 2017-10-08T16:52:49.813 回答
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++ 编写的。将惰性部分添加到您的查询功能中,您就可以开始了。如果您需要访问整个数组,您可能需要重新设计您的查询函数。

于 2015-07-06T17:43:30.857 回答
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) / 2 移动到 l 的父级的右侧。如果 l 是偶数,它是左孩子,并且区间也包括它的父级(除非右边界干扰),所以我们只需通过设置 l = l / 2 来移动它。类似的论点适用于右边界。一旦边界相遇,我们就会停下来。

没有递归,也没有额外的计算,比如找到区间的中间,我们只需要遍历所有需要的节点,所以这非常有效。

于 2020-05-16T15:41:55.553 回答