Nel paese delle lezioni non apprese#4481

Nel paese delle lezioni non apprese (Editoriale)

Per risolvere il problema, dobbiamo elaborare query di due tipi:

La proprietà chiave del MCD è la sua associatività: il MCD di più numeri può essere calcolato a pezzi. Se il segmento [l, r] viene diviso in due parti [l, k] e [k + 1, r], allora

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

Questo è esattamente il tipo di operazione per cui è progettato un albero dei segmenti (segment tree). Ogni vertice memorizza il MCD degli elementi del proprio segmento, e il valore in un vertice interno è uguale al MCD dei valori dei suoi due figli. L'albero dei segmenti permette sia di calcolare il MCD su un segmento sia di modificare un elemento dell'array utilizzando O(\log n) operazioni di MCD.

Esempio

Vediamo come funziona l'algoritmo sull'esempio. La figura a sinistra mostra l'albero dei segmenti per l'array iniziale (2, 4, 6, 10, 8). Ogni vertice mostra il proprio segmento e il MCD degli elementi su di esso. La figura a destra mostra l'albero dopo l'esecuzione di entrambe le query di aggiornamento; i vertici i cui valori sono cambiati sono evidenziati.

Elaboriamo le query una per una:

Otteniamo le risposte 2, 2, 5, 1, che coincidono con la risposta dell'esempio.

Implementazione dell'algoritmo

Dichiariamo l'array SegTree per memorizzare l'albero dei segmenti e l'array per memorizzare la sequenza di numeri in input. Ogni vertice dell'albero memorizza il MCD degli elementi del proprio segmento. Poiché a_i \le 10^9 e il MCD non supera i suoi argomenti, i valori nelle celle di SegTree non superano i limiti del tipo int.

vector<int> SegTree, v;

La funzione BuildTree costruisce l'albero dei segmenti a partire dall'array . Il vertice corrisponde al segmento [lpos, rpos]. Viene utilizzata la funzione standard per calcolare il MCD di due numeri.

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]);
  }
}

La funzione GetGCD restituisce il MCD degli elementi sul segmento [left, right]. Il vertice corrisponde al segmento [lpos, rpos].

Se il segmento della query è vuoto (left > right), restituire — l'elemento neutro per l'operazione di MCD. Se il segmento della query coincide con il segmento del vertice, la risposta è memorizzata in SegTree[v]. Altrimenti, dividere la query nelle parti che ricadono nel figlio sinistro e in quello destro, e restituire il MCD dei risultati.

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));
}

La funzione update assegna il valore all'elemento con indice . Il vertice corrisponde al segmento [lpos, rpos].

Scendere fino alla foglia corrispondente alla posizione e scrivervi il nuovo valore. Durante il ritorno dalla ricorsione, ricalcolare il MCD in tutti i vertici sul percorso dalla foglia alla radice.

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]);
  }
}

La parte principale del programma. Leggiamo la sequenza di numeri in input nell'array , a partire dall'indice .

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

Costruiamo l'albero dei segmenti a partire dagli elementi dell'array v[1 \ldots n]. Un albero dei segmenti su elementi necessita al massimo di celle.

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

Elaboriamo le query una per una. Se q = 1, stampiamo il MCD sul segmento [l, r]. Se q = 2, assegniamo il valore all'elemento con indice .

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);
}

Implementazione in Python

La lista SegTree memorizza l'albero dei segmenti; ciascuno dei suoi vertici contiene il MCD degli elementi del proprio segmento. Importiamo il modulo , che contiene una funzione per calcolare il MCD.

import math

Dichiariamo la funzione gcd, che calcola il MCD di due numeri.

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

La funzione BuildTree costruisce l'albero dei segmenti a partire dalla lista . Il vertice corrisponde al segmento [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])

La funzione GetGCD restituisce il MCD degli elementi sul segmento [left, right]. Il vertice corrisponde al segmento [lpos, rpos].

Se il segmento della query è vuoto (left > right), restituire — l'elemento neutro per l'operazione di MCD. Se il segmento della query coincide con il segmento del vertice, la risposta è memorizzata in SegTree[v]. Altrimenti, dividere la query nelle parti che ricadono nel figlio sinistro e in quello destro, e restituire il MCD dei risultati.

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))

La funzione Update assegna il valore all'elemento con indice . Il vertice corrisponde al segmento [lpos, rpos].

Scendere fino alla foglia corrispondente alla posizione e scrivervi il nuovo valore. Durante il ritorno dalla ricorsione, ricalcolare il MCD in tutti i vertici sul percorso dalla foglia alla radice.

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])

La parte principale del programma. Leggiamo la sequenza di numeri in input nella lista , a partire dall'indice .

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

Costruiamo l'albero dei segmenti a partire dagli elementi della lista v[1 \ldots n]. Un albero dei segmenti su elementi necessita al massimo di celle.

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

Elaboriamo le query una per una. Se q = 1, stampiamo il MCD sul segmento [l, r]; se q = 2, assegniamo il valore all'elemento con indice .

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)