In the Country of Unlearned Lessons#4481

In the Country of Unlearned Lessons (Editorial)

To solve the problem, we need to process queries of two types:

The key property of the GCD is its associativity: the GCD of several numbers can be computed in parts. If the segment [l, r] is split into two parts [l, k] and [k + 1, r], then

\mathrm{GCD}(a_l, \ldots, a_r) = \mathrm{GCD}(\mathrm{GCD}(a_l, \ldots, a_k), \mathrm{GCD}(a_{k+1}, \ldots, a_r))

This is exactly the kind of operation a segment tree is designed for. Each vertex stores the GCD of the elements of its segment, and the value in an internal vertex equals the GCD of the values of its two children. The segment tree allows both computing the GCD on a segment and changing an array element using O(\log n) GCD operations.

Example

Let us consider how the algorithm works on the example. The left figure shows the segment tree for the initial array (2, 4, 6, 10, 8). Each vertex shows its segment and the GCD of the elements on it. The right figure shows the tree after both update queries have been executed; vertices whose values changed are highlighted.

Let us process the queries one by one:

We get the answers 2, 2, 5, 1, which coincide with the answer from the example.

Algorithm implementation

Declare the array SegTree to store the segment tree and the array to store the input sequence of numbers. Each vertex of the tree stores the GCD of the elements of its segment. Since a_i \le 10^9 and the GCD does not exceed its arguments, the values in the cells of SegTree do not exceed the limits of the int type.

vector<int> SegTree, v;

The function BuildTree builds the segment tree from the array . Vertex corresponds to the segment [lpos, rpos]. The standard function is used to compute the GCD of two numbers.

void BuildTree(vector<int> &a, int v, int lpos, int rpos)
{
  if (lpos == rpos) SegTree[v] = a[lpos];
  else
  {
    int mid = (lpos + rpos) / 2;
    BuildTree(a, 2 * v, lpos, mid);
    BuildTree(a, 2 * v + 1, mid + 1, rpos);
    SegTree[v] = gcd(SegTree[2 * v], SegTree[2 * v + 1]);
  }
}

The function GetGCD returns the GCD 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 the GCD operation. If the query segment coincides with the segment of the vertex, the answer is stored in SegTree[v]. Otherwise, split the query into the parts that fall into the left and right children, and return the GCD of the results.

int GetGCD(int v, int lpos, int rpos, int left, int right)
{
  if (left > right) return 0;
  if ((left == lpos) && (right == rpos)) return SegTree[v];
  int mid = (lpos + rpos) / 2;
  return gcd(GetGCD(2 * v, lpos, mid, left, min(right, mid)),
         GetGCD(2 * v + 1, mid + 1, rpos, max(left, mid + 1), right));
}

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

Descend to the leaf corresponding to position and write the new value into it. When returning from the recursion, recompute the GCD in all vertices on the path from the leaf to the root.

void update(int v, int lpos, int rpos, int pos, int val)
{
  if (lpos == rpos) SegTree[v] = val;
  else
  {
    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);
    SegTree[v] = gcd(SegTree[2 * v], SegTree[2 * v + 1]);
  }
}

The main part of the program. Read the input sequence of numbers into the array , starting from index .

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

Build the segment tree from the elements of the array v[1 \ldots n]. A segment tree on elements needs at most cells.

SegTree.resize(4 * n);
BuildTree(v,1,1,n);

Process the queries one by one. If q = 1, print the GCD on the segment [l, r]. If q = 2, assign the value to the element with index .

scanf("%d",&m);
for(i = 0; i < m; i++)
{
  scanf("%d %d %d",&q,&l,&r);
  if (q == 1) printf("%d\n",GetGCD(1,1,n,l,r));
  else update(1,1,n,l,r);
}

Python implementation

The list SegTree stores the segment tree; each of its vertices contains the GCD of the elements of its segment. Import the module, which contains a function for computing the GCD.

import math

Declare the function gcd, which computes the GCD of two numbers.

def gcd(a, b):
  return math.gcd(a, b)

The function BuildTree builds the segment tree from the list . Vertex corresponds to the segment [lpos, rpos].

def BuildTree(a, v, lpos, rpos):
  if lpos == rpos:
    SegTree[v] = a[lpos]
  else:
    mid = (lpos + rpos) // 2
    BuildTree(a, 2 * v, lpos, mid)
    BuildTree(a, 2 * v + 1, mid + 1, rpos)
    SegTree[v] = gcd(SegTree[2 * v], SegTree[2 * v + 1])

The function GetGCD returns the GCD 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 the GCD operation. If the query segment coincides with the segment of the vertex, the answer is stored in SegTree[v]. Otherwise, split the query into the parts that fall into the left and right children, and return the GCD of the results.

def GetGCD(v, lpos, rpos, left, right):
  if left > right:
    return 0
  if left == lpos and right == rpos:
    return SegTree[v]
  mid = (lpos + rpos) // 2
  return gcd(GetGCD(2 * v, lpos, mid, left, min(right, mid)),
      GetGCD(2 * v + 1, mid + 1, rpos, max(left, mid + 1),right))

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

Descend to the leaf corresponding to position and write the new value into it. When returning from the recursion, recompute the GCD in all vertices on the path from the leaf to the root.

def Update(v, lpos, rpos, pos, val):
  if lpos == rpos:
    SegTree[v] = val
  else:
    mid = (lpos + rpos) // 2
    if pos <= mid:
      Update(2 * v, lpos, mid, pos, val)
    else:
      Update(2 * v + 1, mid + 1, rpos, pos, val)
    SegTree[v] = gcd(SegTree[2 * v], SegTree[2 * v + 1])

The main part of the program. Read the input sequence of numbers into the list , starting from index .

n = int(input())
v = [0] + list(map(int, input().split()))

Build the segment tree from the elements of the list v[1 \ldots n]. A segment tree on elements needs at most cells.

SegTree = [0] * (4 * n)
BuildTree(v, 1, 1, n)

Process the queries one by one. If q = 1, print the GCD on the segment [l, r]; if q = 2, assign the value to the element with index .

m = int(input())
for _ in range(m):
  q, l, r = map(int, input().split())
  if q == 1:
    print(GetGCD(1, 1, n, l, r))
  else:
    Update(1, 1, n, l, r)