Öyrənilməmiş dərslər ölkəsində#4481

Öyrənilməmiş dərslər ölkəsində (Həll təhlili)

Məsələni həll etmək üçün iki növ sorğunu emal etmək lazımdır:

ƏBOB-un əsas xüsusiyyəti onun assosiativliyidir: bir neçə ədədin ƏBOB-u hissələr şəklində hesablana bilər. Əgər [l, r] seqmenti iki hissəyə — [l, k] və [k + 1, r] — bölünərsə, onda

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

Məhz bu cür əməliyyat üçün seqment ağacı nəzərdə tutulmuşdur. Hər bir təpə öz seqmentinin elementlərinin ƏBOB-unu saxlayır, daxili təpədəki dəyər isə onun iki övladının dəyərlərinin ƏBOB-una bərabərdir. Seqment ağacı həm seqment üzərində ƏBOB-un hesablanmasına, həm də massiv elementinin dəyişdirilməsinə O(\log n) ƏBOB əməliyyatı sərf olunmaqla imkan verir.

Misal

Alqoritmin misal üzərində necə işlədiyinə baxaq. Soldakı şəkildə ilkin (2, 4, 6, 10, 8) massivi üçün seqment ağacı göstərilmişdir. Hər bir təpədə onun seqmenti və bu seqmentin elementlərinin ƏBOB-u göstərilir. Sağdakı şəkildə isə hər iki dəyişdirmə sorğusu icra edildikdən sonrakı ağac göstərilir; dəyəri dəyişmiş təpələr xüsusi işarələnmişdir.

Sorğuları bir-bir emal edək:

Biz 2, 2, 5, 1 cavablarını alırıq ki, bu da misaldakı cavabla üst-üstə düşür.

Alqoritmin realizasiyası

Seqment ağacını saxlamaq üçün SegTree massivini, giriş ədədlər ardıcıllığını saxlamaq üçün isə massivini elan edək. Ağacın hər bir təpəsi öz seqmentinin elementlərinin ƏBOB-unu saxlayır. a_i \le 10^9 olduğundan və ƏBOB öz arqumentlərindən böyük olmadığından, SegTree-nin xanalarındakı dəyərlər int tipinin hüdudlarından kənara çıxmır.

vector<int> SegTree, v;

BuildTree funksiyası massivindən seqment ağacını qurur. təpəsi [lpos, rpos] seqmentinə uyğun gəlir. İki ədədin ƏBOB-unu hesablamaq üçün standart funksiyasından istifadə olunur.

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

GetGCD funksiyası [left, right] seqmenti üzərindəki elementlərin ƏBOB-unu qaytarır. təpəsi [lpos, rpos] seqmentinə uyğun gəlir.

Əgər sorğu seqmenti boşdursa (left > right), qaytarılır — ƏBOB əməliyyatı üçün neytral element. Əgər sorğu seqmenti təpənin seqmenti ilə üst-üstə düşürsə, cavab SegTree[v]-də saxlanılır. Əks halda, sorğu sol və sağ övladlara düşən hissələrə bölünür və nəticələrin ƏBOB-u qaytarılır.

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

update funksiyası indeksli elementə dəyərini mənimsədir. təpəsi [lpos, rpos] seqmentinə uyğun gəlir.

mövqeyinə uyğun yarpağa enmək və ona yeni dəyəri yazmaq lazımdır. Rekursiyadan qayıdarkən yarpaqdan kökə qədər olan yol üzərindəki bütün təpələrdə ƏBOB yenidən hesablanır.

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

Proqramın əsas hissəsi. Giriş ədədlər ardıcıllığını -ci indeksdən başlayaraq massivinə oxuyuruq.

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

v[1 \ldots n] massivinin elementlərindən seqment ağacını qururuq. elementdən ibarət seqment ağacı üçün ən çoxu xana lazımdır.

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

Sorğuları bir-bir emal edirik. Əgər q = 1 olarsa, [l, r] seqmenti üzərindəki ƏBOB-u çap edirik. Əgər q = 2 olarsa, indeksli elementə dəyərini mənimsədirik.

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 üzərində realizasiya

SegTree siyahısı seqment ağacını saxlayır; onun hər bir təpəsi öz seqmentinin elementlərinin ƏBOB-unu özündə saxlayır. ƏBOB-un hesablanması üçün funksiya daxil olan modulunu idxal edirik.

import math

İki ədədin ƏBOB-unu hesablayan gcd funksiyasını elan edək.

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

BuildTree funksiyası siyahısından seqment ağacını qurur. təpəsi [lpos, rpos] seqmentinə uyğun gəlir.

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

GetGCD funksiyası [left, right] seqmenti üzərindəki elementlərin ƏBOB-unu qaytarır. təpəsi [lpos, rpos] seqmentinə uyğun gəlir.

Əgər sorğu seqmenti boşdursa (left > right), qaytarılır — ƏBOB əməliyyatı üçün neytral element. Əgər sorğu seqmenti təpənin seqmenti ilə üst-üstə düşürsə, cavab SegTree[v]-də saxlanılır. Əks halda, sorğu sol və sağ övladlara düşən hissələrə bölünür və nəticələrin ƏBOB-u qaytarılır.

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

Update funksiyası indeksli elementə dəyərini mənimsədir. təpəsi [lpos, rpos] seqmentinə uyğun gəlir.

mövqeyinə uyğun yarpağa enmək və ona yeni dəyəri yazmaq lazımdır. Rekursiyadan qayıdarkən yarpaqdan kökə qədər olan yol üzərindəki bütün təpələrdə ƏBOB yenidən hesablanır.

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

Proqramın əsas hissəsi. Giriş ədədlər ardıcıllığını -ci indeksdən başlayaraq siyahısına oxuyuruq.

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

v[1 \ldots n] siyahısının elementlərindən seqment ağacını qururuq. elementdən ibarət seqment ağacı üçün ən çoxu xana lazımdır.

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

Sorğuları bir-bir emal edirik. Əgər q = 1 olarsa, [l, r] seqmenti üzərindəki ƏBOB-u çap edirik; əgər q = 2 olarsa, indeksli elementə dəyərini mənimsədirik.

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)