Максимум#4473

Максимум (Розбір)

Задача вимагає відповідати на запити максимуму на відрізку. Елементи масиву не змінюються, тому підходить статична структура даних. Кількість запитів сягає 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 будує розріджену таблицю з масиву :

mas[i][j] = \max(b[i], b[i + 1], \ldots, b[i + 2^j - 1])
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));
}