Для розв'язання задачі потрібно обробляти запити двох типів:
обчислення найбільшого спільного дільника (НСД) елементів на відрізку;
зміна одного елемента масиву.
Ключова властивість НСД — його асоціативність: НСД кількох чисел можна обчислювати частинами. Якщо відрізок [l, r] розбити на дві частини [l, k] та [k + 1, r], то
Саме для такого роду операцій і призначене дерево відрізків. У кожній вершині зберігається НСД елементів її відрізка, а значення у внутрішній вершині дорівнює НСД значень її двох дітей. Дерево відрізків дозволяє як обчислювати НСД на відрізку, так і змінювати елемент масиву за допомогою O(\log n) операцій обчислення НСД.
Приклад
Розглянемо, як працює алгоритм на прикладі. На лівому малюнку показано дерево відрізків для початкового масиву (2, 4, 6, 10, 8). У кожній вершині вказано її відрізок та НСД елементів на ньому. На правому малюнку показано дерево після виконання обох запитів зміни; вершини, значення яких змінилися, виділено.
Обробимо запити по черзі:
: відрізок збігається з відрізком кореня, тому відповідь зберігається в корені і дорівнює ;
: відрізок розбивається на частини та , відповідь — \mathrm{GCD}(4, 6) = 2;
: елемент a_5 стає рівним . Перераховуються вершини на шляху від листка до кореня: у вершині отримуємо \mathrm{GCD}(10, 15) = 5, у корені — \mathrm{GCD}(2, 5) = 1;
: елемент a_3 стає рівним . У вершині отримуємо \mathrm{GCD}(2, 10) = 2, у корені — \mathrm{GCD}(2, 5) = 1;
: відрізок розбивається на частини та , відповідь — \mathrm{GCD}(10, 5) = 5;
: відповідь зберігається в корені і дорівнює .
Отримуємо відповіді 2, 2, 5, 1, що збігаються з відповіддю з прикладу.
Реалізація алгоритму
Оголосимо масив SegTree для зберігання дерева відрізків та масив для зберігання вхідної послідовності чисел. У кожній вершині дерева зберігається НСД елементів її відрізка. Оскільки a_i \le 10^9, а НСД не перевищує своїх аргументів, значення в комірках SegTree не виходять за межі типу int.
vector<int> SegTree, v;Функція BuildTree будує дерево відрізків із масиву . Вершина відповідає відрізку [lpos, rpos]. Для обчислення НСД двох чисел використовується стандартна функція .
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 повертає НСД елементів на відрізку [left, right]. Вершина відповідає відрізку [lpos, rpos].
Якщо запитуваний відрізок порожній (left > right), повертаємо — нейтральний елемент для операції НСД. Якщо запитуваний відрізок збігається з відрізком вершини, відповідь зберігається в SegTree[v]. Інакше розбиваємо запит на частини, що потрапляють у лівого та правого дітей, і повертаємо НСД результатів.
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 присвоює значення елементу з індексом . Вершина відповідає відрізку [lpos, rpos].
Спускаємося до листка, що відповідає позиції , і записуємо в нього нове значення. При поверненні з рекурсії перераховуємо НСД у всіх вершинах на шляху від листка до кореня.
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]);
}
}Основна частина програми. Зчитуємо вхідну послідовність чисел у масив , починаючи з індексу .
scanf("%d", &n);
v.resize(n + 1);
for (i = 1; i <= n; i++)
scanf("%d", &v[i]);Будуємо дерево відрізків з елементів масиву v[1 \ldots n]. Дереву відрізків на елементів потрібно не більше комірок.
SegTree.resize(4 * n);
BuildTree(v,1,1,n);Обробляємо запити по черзі. Якщо q = 1, виводимо НСД на відрізку [l, r]. Якщо q = 2, присвоюємо значення елементу з індексом .
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
Список SegTree зберігає дерево відрізків; у кожній його вершині міститься НСД елементів її відрізка. Імпортуємо модуль , який містить функцію обчислення НСД.
import mathОголосимо функцію gcd, яка обчислює НСД двох чисел.
def gcd(a, b):
return math.gcd(a, b)Функція BuildTree будує дерево відрізків зі списку . Вершина відповідає відрізку [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])Функція GetGCD повертає НСД елементів на відрізку [left, right]. Вершина відповідає відрізку [lpos, rpos].
Якщо запитуваний відрізок порожній (left > right), повертаємо — нейтральний елемент для операції НСД. Якщо запитуваний відрізок збігається з відрізком вершини, відповідь зберігається в SegTree[v]. Інакше розбиваємо запит на частини, що потрапляють у лівого та правого дітей, і повертаємо НСД результатів.
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 присвоює значення елементу з індексом . Вершина відповідає відрізку [lpos, rpos].
Спускаємося до листка, що відповідає позиції , і записуємо в нього нове значення. При поверненні з рекурсії перераховуємо НСД у всіх вершинах на шляху від листка до кореня.
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])Основна частина програми. Зчитуємо вхідну послідовність чисел у список , починаючи з індексу .
n = int(input())
v = [0] + list(map(int, input().split()))Будуємо дерево відрізків з елементів списку v[1 \ldots n]. Дереву відрізків на елементів потрібно не більше комірок.
SegTree = [0] * (4 * n)
BuildTree(v, 1, 1, n)Обробляємо запити по черзі. Якщо q = 1, виводимо НСД на відрізку [l, r]; якщо q = 2, присвоюємо значення елементу з індексом .
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)