Задача вимагає відповідати на запити максимуму на відрізку. Елементи масиву не змінюються, тому підходить статична структура даних. Кількість запитів сягає 5 \cdot 10^5, а довжина масиву — 10^5. Обчислення максимуму на кожному відрізку напряму вимагатиме до 5 \cdot 10^{10} операцій, що занадто повільно.
Використаємо sparse table (розріджену таблицю) для задачі RMQ, але замість мінімуму шукатимемо максимум (Range Maximum Query). Максимум, як і мінімум, не змінюється, якщо певний елемент порахувати двічі. Тому відрізок запиту можна покрити двома відрізками довжини 2^k, що перекриваються, і вибрати більший з їхніх максимумів.
Клітинка таблиці може зберігати або індекс максимального елемента, або сам максимум. За умовою задачі потрібно виводити лише значення, тому достатньо зберігати максимум. Варіант з індексом потрібен, коли треба знати позицію максимуму. Нижче наведено обидві реалізації.
За умовою задачі 1 \le l, r \le n, і не гарантується, що l \le r. Тому перед виконанням запиту, за потреби, межі міняємо місцями.
Складність. Побудова розрідженої таблиці займає O(n \log n), а кожен запит — . Загальна складність — O(n \log n + q).
Приклад
Розглянемо масив з прикладу: (5, 1, 2, 8, 7, 6, 10, 7, 5, 6). Таблиця , у якій mas[i][j] — максимум на відрізку [i; i + 2^j - 1], наведена нижче. Прочерк означає, що відрізок виходить за межі масиву.
\boldsymbol{j \setminus i} | ||||||||||
— | ||||||||||
— | — | — | ||||||||
— | — | — | — | — | — | — |
Наприклад, mas[4][2] = \max(mas[4][1], mas[6][1]) = \max(8, 10) = 10 — це максимум на відрізку .
Розглянемо запит . Довжина відрізка дорівнює , а найбільший степінь двійки, що не перевищує , дорівнює , тому k = 2. Відрізок покривається відрізками та , які перекриваються на відрізку :
Відповідь дорівнює \max(mas[2][2], mas[3][2]) = \max(8, 8) = 8. Елемент належить обом відрізкам, але це не впливає на значення максимуму.
Усі запити з прикладу наведено в таблиці:
запит | відрізки | максимуми | відповідь | |
, | ||||
, | ||||
, | ||||
, | ||||
, | ||||
, | ||||
, | ||||
, |
Для відрізків довжини та (запити , та ) обидва покривальні відрізки збігаються з самим відрізком запиту.
Реалізація алгоритму — індекс максимуму
Вхідна послідовність a_i (n \le 10^5) зберігається в масиві , починаючи з індексу . У цьому варіанті клітинка mas[i][j] зберігає індекс максимального елемента на відрізку [i; i + 2^j - 1], а сам максимум дорівнює a[mas[i][j]]. Оскільки 2^{16} \le 10^5 < 2^{17}, для другого виміру достатньо LOGMAX = 17.
#define MAX 100010
#define LOGMAX 17
int mas[MAX][LOGMAX];
int a[MAX];Функція Build_RMQ_Array будує розріджену таблицю індексів.
void Build_RMQ_Array()
{Для j = 0 відрізок складається з одного елемента, тому mas[i][0] = i.
for (int i = 1; i <= n; i++) mas[i][0] = i;Для j > 0 порівнюємо елементи масиву з індексами mas[i][j - 1] та mas[i + 2^{j-1}][j - 1] — максимуми на двох половинах відрізка — і зберігаємо індекс більшого з них.
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
if (a[mas[i][j - 1]] > a[mas[i + (1 << (j - 1))][j - 1]])
mas[i][j] = mas[i][j - 1];
else mas[i][j] = mas[i + (1 << (j - 1))][j - 1];
}Функція RangeMaxQuery повертає індекс максимального елемента на відрізку .
int RangeMaxQuery(int i, int j)
{
int temp, k = 0;Якщо i > j, межі міняються місцями.
if (i > j) temp = i, i = j, j = temp;Далі, в циклі, знаходимо найбільше таке, що 2^k \le j - i + 1.
while ((1 << (k + 1)) <= j - i + 1) k++;Порівнюємо елементи з індексами mas[i][k] та mas[j - 2^k + 1][k] і повертаємо індекс більшого з них.
return (a[mas[i][k]] >= a[mas[j - (1 << k) + 1][k]]) ?
mas[i][k] : mas[j - (1 << k) + 1][k];
}Основна частина програми. Зчитуємо розмір масиву та його елементи, починаючи з індексу .
scanf("%d", &n);
for (i = 1; i <= n; i++)
scanf("%d", &a[i]);Будуємо розріджену таблицю з масиву .
Build_RMQ_Array();Зчитуємо кількість запитів .
scanf("%d", &q);Для кожного запиту функція RangeMaxQuery повертає індекс максимального елемента на відрізку , і ми виводимо сам елемент .
for (i = 0; i < q; i++)
{
scanf("%d %d", &u, &v);
int index = RangeMaxQuery(u, v);
printf("%d\n", a[index]);
}Реалізація алгоритму — максимум
Вхідна послідовність a_i (n \le 10^5) зберігається в масиві , починаючи з індексу . У цьому варіанті клітинка mas[i][j] зберігає сам максимум на відрізку [i; i + 2^j - 1]. Оскільки 2^{16} \le 10^5 < 2^{17}, для другого виміру достатньо LOGMAX = 17.
#define MAX 100010
#define LOGMAX 17
int mas[MAX][LOGMAX];
int a[MAX];Функція Build_RMQ_Array будує розріджену таблицю з масиву :
void Build_RMQ_Array(int *b)
{Для j = 0 розріджена таблиця містить вихідні елементи масиву: mas[i][0] = b[i].
for (int i = 1; i <= n; i++) mas[i][0] = b[i];Для j > 0 значення mas[i][j] дорівнює більшому з максимумів на двох половинах відрізка.
for (int j = 1; 1 << j <= n; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
mas[i][j] = max(mas[i][j - 1], mas[i + (1 << (j - 1))][j - 1]);
}Функція RangeMaxQuery повертає максимум на відрізку .
int RangeMaxQuery(int i, int j)
{
int temp, k = 0;Якщо i > j, межі міняються місцями.
if (i > j) temp = i, i = j, j = temp;В циклі знаходимо найбільше таке, що 2^k \le j - i + 1. Такий спосіб обчислення не вимагає функції логарифма і не залежить від похибок обчислень з рухомою комою.
while ((1 << (k + 1)) <= j - i + 1) k++;Відповідь — більший з максимумів на відрізках [i; i + 2^k - 1] та [j - 2^k + 1; j].
return max(mas[i][k],mas[j - (1<<k) + 1][k]);
}Основна частина програми. Зчитуємо розмір масиву та його елементи, починаючи з індексу .
scanf("%d",&n);
for(i = 1; i <= n; i++) scanf("%d",&a[i]);Будуємо розріджену таблицю з масиву .
Build_RMQ_Array(a);Зчитуємо кількість запитів . Для кожного запиту виводимо максимум на відрізку .
scanf("%d",&q);
for(i = 0; i < q; i++)
{
scanf("%d %d",&u,&v);
printf("%d\n",RangeMaxQuery(u,v));
}