Dima and array#2941

Dima and array (Editorial)

The problem requires processing operations of two types:

We need a data structure that performs both operations quickly. Such a structure is a segment tree for sums. Each of its vertices stores the sum of the elements of its segment, and the value in an internal vertex equals the sum of the values of its two children.

The segment tree allows both computing the sum on a segment and changing an array element in O(\log n).

Example

Let us simulate the queries from the example. Initially, the array is (1, 2, 3).

Let us see how the segment tree changes. The left figure shows the tree for the initial array, and the right figure shows it after the assignment a_3 = 2. Each vertex shows its segment and the sum of the elements on it; the changed vertices are highlighted.

The segment of both queries coincides with the segment of the root, so the answer is taken directly from the root: before the assignment and after it. During the assignment a_3 = 2, only the vertices on the path from the leaf to the root are recomputed: the leaf receives the value , and the root receives the value 3 + 2 = 5.

Algorithm implementation

Store the segment tree in the vector SegTree of length 4 \cdot MAX, where MAX = 5 \cdot 10^5 is the maximum number of array elements. Each vertex of the tree stores the sum of the elements of its segment.

#define MAX 500000
vector<int> SegTree(4*MAX,0);

The function BuildTree builds the segment tree from the array . It receives the number of the current vertex and the bounds and of the segment corresponding to this vertex.

void BuildTree(vector<int>&a, int v, int lpos, int rpos)
{

If the segment of vertex consists of one element (lpos = rpos), then is a leaf of the tree. Write the value into it.

  if (lpos == rpos) SegTree[v] = a[lpos];
  else
  {

Otherwise, find the middle of the segment mid = (lpos + rpos) / 2.

    int mid = (lpos + rpos) / 2;

The segment [lpos; rpos] is split into two subsegments [lpos; mid] and [mid + 1; rpos]. Recursively build the tree for the left child and the right child 2v + 1.

    BuildTree(a, 2*v, lpos, mid);
    BuildTree(a, 2*v+1, mid + 1, rpos);

The value in vertex equals the sum of the values of its children.

    SegTree[v] = SegTree[2*v] + SegTree[2*v+1];
  }
}

The function Update assigns the value to the element with index . Vertex corresponds to the segment [lpos; rpos].

void Update(int v, int lpos, int rpos, int pos, int val)
{

If vertex is a leaf (lpos = rpos), it corresponds to position . Write the value into it.

  if (lpos == rpos) SegTree[v] = val;
  else
  {

Otherwise, determine which half of the segment contains position , and recursively descend into the left or right child.

    int mid = (lpos + rpos) / 2;
    if (pos <= mid)
      Update (2*v, lpos, mid, pos, val);
    else
      Update (2*v+1, mid+1, rpos, pos, val);

After returning from the recursion, recompute the sum in vertex . In this way, all vertices on the path from the leaf to the root are updated.

    SegTree[v] = SegTree[2*v] + SegTree[2*v+1];
  }
}

The function Summa returns the sum of the elements on the segment [left; right]. Vertex corresponds to the segment [lpos; rpos]. If the query segment is empty (left > right), return — the neutral element for addition.

int Summa(int v, int lpos, int rpos, int left, int right)
{
  if (left > right) return 0;

If the query segment coincides with the segment of the vertex, the answer is stored in SegTree[v].

  if ((left == lpos) && (right == rpos))
    return SegTree[v];

Otherwise, find the middle of the segment mid = (lpos + rpos) / 2.

  int mid = (lpos + rpos) / 2;

Split the query segment into two parts: [left; \min(right, mid)] lies in the segment of the left child, and [\max(left, mid + 1); right] lies in the segment of the right child. One of the parts may be empty. Return the sum of the results for both children.

  return Summa(2*v, lpos, mid, left, min(right,mid)) +
         Summa(2*v+1, mid+1, rpos, max(left,mid+1), right);
}

The main part of the program. Read the size of the array , the number of queries , and the array elements into the vector , starting from index . The call scanf("\n") skips the line break after the array, so that the next character read is the operation sign.

scanf("%d %d",&n,&q);
v.resize(n+1);
for(i = 1; i <= n; i++)
  scanf("%d",&v[i]);
scanf("\n");

Build the segment tree from the elements v[1 \ldots n].

BuildTree(v,1,1,n);

Process the queries one by one. First, read the operation character ; the space in the format "%c " skips the space after it. If = '=', assign the value to the element with index . Otherwise, compute and print the sum on the segment . The \n character in the format "%d %d\n" skips the line break, so the next read into gets the sign of the next operation.

for(j = 0; j < q; j++)
{
  scanf("%c ",&c);
  if (c == '=')
  {
    scanf("%d %d\n",&i,&d);
    Update(1,1,n,i,d);
  } else
  {
    scanf("%d %d\n",&f,&t);
    printf("%d\n",Summa(1,1,n,f,t));
  }
}