1

The problem is pretty much what the title says. There is an n-element(n<10^5) array, which consists of n zeros. There are q operations (q<2*10^5): Each operation can be one of two below:

1. Add x to all elements on a [l,r] range(x can be also negative)
2. Ask for the number of zeros on a [l,r] range

Note that it is guaranteed that absolute values in the array will never get greater than 10^5

I am asking this question because I was reading a solution to another problem where my question was its subproblem. The author said that it can be solved using segment tree with lazy propagation. I can not figure out how to do that. The brute force solution(O(q*n)) is too slow...

What is the most efficient way to implement answering the query considering the first operation? O(q*long(n)) is what I would be guessing.

Example:

 The array is: 0 0 0 0
-> Add 3 from index 2 to index 3:
    The array is now: 0 0 3 3
-> Ask about number of zeros on [1,2]
    The answer is 1
-> Add -3 from index 3 to index 3
    The array is now: 0 0 3 0
-> Ask about number of zeros on [0,3]
    The answer is 3
dispenomi
  • 27
  • 5
  • You mentioned segment tree, so you are looking for `O(n log n)` solution? Or is `O(n * sqrt(n))` also ok? Please write more details about the problem, target time complexity and parameter constraints with time limits are very important. – Sandro J Jan 03 '23 at 00:09
  • @SandroJ thank you for your response! I will edit this post quickly to add parameter constraints. O(n*sqrt(n)) is acceptable, but I am quite sure that it can be solved faster. I was actually reading solution to a problem where my case was its subproblem The author used segment tree with lazy propagation to solve it, but it was not explained how. – dispenomi Jan 03 '23 at 00:27
  • If you literally have to change the contents of the array for the `add` operation, then the best possible performance for a series of `add` operations that change a large chunk of the array will come out to `O(q*n)`. Are you required to actually use and alter the given array, or is the array more conceptual, and any structure will work so long as it provides the correct outputs on the query operation? – StriplingWarrior Jan 03 '23 at 06:07
  • @StriplingWarrior I just need correct answers. Lazy propagation also does’t alter the whole segment(at least until it’s not obligatory) – dispenomi Jan 03 '23 at 07:26
  • What happen if you add more than 9 or below 0? does it cycle back or goes to the next element or it can't happen (there's a restriction)? – JackRaBeat Jan 03 '23 at 14:42
  • @JackRaBeat I am sorry... The example might be misleading because there are no spaces between each element. I will correct that. You can add any value there but the absolute value in any of those cells won't get lower than 10^5. And you can add element that is greater than 9. – dispenomi Jan 03 '23 at 15:13

1 Answers1

0

Ok, I have solved this task. All we have to do is create a segment tree of minimums with lazy propagation, which also counts number of those minimums. In each node of our segment tree we will store 3 values:

1. Minimum from the segment operated by our node.
2. Number of those minimums on a segment operated by our node.
3. Lazy propagation values(values which tell us what should we pass to our sons when visiting this node next time).

When reading from a segment we will get:

1.Minimum on this segment 
2.How many numbers are equal to the minimum on this segment.

If segment's minimum is 0, then we have to simply return the second value. If our minimum is higher than 0, the answer is 0(no zeros found on this segment, because the lowest number is higher than 0). Since read operation, as well as update operations, is O(log(n)), and we have q operations, the complexity of this algorithm is O(q*log(n)), which is sufficient.

Pseudocode:

min_count[2*MAX_N]
val[2*MAX_N]
lazy[2*MAX_N]

values_from_sons(node)
{
    if(node has no childern) stop the function
    
    val[node]=min(val[2*node],val[2*node+1] //it is a segment tree of minimums

    if(val[2*node]<val[2*node+1]) //minimum from the left son  <  minimum from the right son
    {
        min_count[node]=min_count[2*node]
        stop the function
    }

    if(val[2*node]<val[2*node+1]) //minimum from the left son  >  minimum from the right son
    {
        min_count[node]=min_count[2*node]
        stop the function
    }
    if(val[2*node]==val[2*node+1])
    {
        min_count[node]=min_count[2*node]+min_count[2*node+1]; 
        //we have x minimums in the left son, and y non-intersecting with x minimums on the right, so we can sum them up
    }
}

pass(node)
{
    if(node has no childern) stop the function

    //we are passing values to our children when visiting node,
    // remember that array "lazy" stores values which belong to node's sons

    val[2*node]+=lazy[node];
        lazy[2*node]+=lazy[node];

        val[2*node+1]+=lazy[node];
        lazy[2*node+1]+=lazy[node];

        lazy[node]=0;
}

update(node,left,right,s1,s2,add) 
//node-number of a node, [left,right]-segment operated by this node, [s1,s2]-segment on which we want to add "add" value
{
    pass(node)

    if([left,right] and [s1,s2] have no intersections) stop the function
    
    if([left,right] and [s1,s2] have at least one intersection) /// add "add" value to this node's lazy and val
    {
        val[node]+=add
        lazy[node]+=add
        stop the function
    }

    update(values of the left son)
    update(values of the right son)

    values_from_sons(node) 
    //placing this function here updates this node's values when some of his lower ancestors were changed
}

read(node,left,right,s1,s2) 
//node-number of a node, [left,right]-segment operated by this node, [s1,s2]-segment for which we want an answer
// this function returns 2 values - minimum from a [s1,s2] segment, and number of values equal to this minimum
{
    pass(node)

    if([left,right] and [s1,s2] have no intersections) return {INF,0}; //return neutral value of min operation
    
    if([left,right] and [s1,s2] have at least one intersection) return {val[node],min_count[node]}

    vl=read(values of the left son)
    vr=read(values of the right son)

    if(vl<vr)
    {
        //vl has lower minimums, so the answer for this node will be vl
        return vl
    }
    else if(vl>vr)
    {
        //vr has lower minimums, so the answer for this node will be vr
        return vr
    }
    else
    {
        //left and right son have the same minimum, and non intersecting values. Hence we can add them
        return {vl's minimum, vl's count of minimums + vr's count of minimums};
    }

}

ini()
//builds tree. remember that you have to use it before using any of the functions above
{
    //Hence we don't have to worry about beginning values, all of them are set to 0 at the beginning,
    // we just have to set min_count table properly
    for(each leaf[node that has no sons])
    {
        min_cout[leaf]=1;
    }
    for(x=MAX_N-1, x>0, x--)
    {
        min_count[x]=min_count[2*x]+min_count[2*x+1]
    }
}
dispenomi
  • 27
  • 5
  • What if the lowest number is lower than zero? – StriplingWarrior Jan 03 '23 at 23:16
  • @StriplingWarrior Another mistake... I didn't add that in the problem I was solving it was guaranteed that those values will never get negative. Should I edit the question so that it satisfies my solution or just delete my approach and leave the problem unsolved? – dispenomi Jan 04 '23 at 00:40
  • I would just update the question with the additional information. – StriplingWarrior Jan 05 '23 at 23:19