Розріджені таблиці#691

Розріджені таблиці (Розбір)

Задача вимагає багаторазово знаходити мінімум на відрізку масиву, який не змінюється після генерації. Кількість запитів сягає 10^7, тому кожен запит потрібно обробляти за . Для цього використовується sparse table — статична структура даних для задачі RMQ (Range Minimum Query).

Побудова таблиці. Для кожної позиції та кожного такого, що i + 2^j - 1 \le n, у клітинці mas[i][j] зберігається індекс мінімального елемента на відрізку [i; i + 2^j - 1] довжини 2^j. Для j = 0 відрізок складається з одного елемента, тому mas[i][0] = i.

Для j > 0 розіб'ємо відрізок довжини 2^j на дві половини довжини 2^{j-1}: [i; i + 2^{j-1} - 1] та [i + 2^{j-1}; i + 2^j - 1]. Індекси мінімумів на половинах уже обчислено — це mas[i][j - 1] та mas[i + 2^{j-1}][j - 1]:

Мінімум усього відрізка дорівнює меншому з двох мінімумів половин. Тому mas[i][j] дорівнює тому з індексів mas[i][j - 1] та mas[i + 2^{j-1}][j - 1], який вказує на менший елемент масиву. Таблиця заповнюється у порядку зростання і містить O(n \log n) клітинок.

Відповідь на запит. Нехай потрібно знайти мінімум на відрізку . Нехай k = \lfloor \log_2(v - u + 1) \rfloor — показник найбільшого степеня двійки, що не перевищує довжину відрізка. Відрізок довжини 2^k, що починається з , та відрізок довжини 2^k, що закінчується в , разом повністю покривають , оскільки 2 \cdot 2^k > v - u + 1:

Ці відрізки можуть перекриватися, але для мінімуму це не має значення: елемент, врахований двічі, не змінює результат (на відміну, наприклад, від суми). Тому мінімум на дорівнює меншому з елементів a[mas[u][k]] та a[mas[v - 2^k + 1][k]], а запит обробляється за .

Межі та згенерованих запитів можуть надходити у будь-якому порядку, тому перед виконанням запиту ми міняємо їх місцями, якщо потрібно.

Складність. Побудова таблиці займає O(n \log n), а кожен запит — . Загальна складність становить O(n \log n + m). При m \le 10^7 саме обробка запиту за дозволяє вкластися в обмеження за часом.

Приклад

Розглянемо приклад: n = 10, m = 8, a_1 = 12345. Згенерований масив збігається з масивом, наведеним в умові задачі. Таблиця для нього наведена нижче. Клітинки містять індекси мінімальних елементів; прочерк означає, що відрізок виходить за межі масиву.

\boldsymbol{j \setminus i}

—

—

—

—

—

—

—

—

—

—

—

Наприклад, mas[3][2] = 5: на відрізку мінімальним є елемент a_5 = 1565158. Значення mas[2][3] = 2 отримане з mas[2][2] = 2 та mas[6][2] = 8, оскільки a_2 = 305498 < a_8 = 570265.

Перший запит: u_1 = 3, v_1 = 9. Довжина відрізка дорівнює , тому k = 2. Відрізок покривається відрізками та , для яких mas[3][2] = 5 та mas[6][2] = 8. Оскільки a_8 = 570265 < a_5 = 1565158, відповідь ans_1 = 570265.

Параметри другого запиту:

u_2 = ((17 \cdot 3 + 751 + 570265 + 2) \bmod 10) + 1 = 10, \\ v_2 = ((13 \cdot 9 + 593 + 570265 + 5) \bmod 10) + 1 = 1.

Оскільки u_2 > v_2, межі міняються місцями, і обчислюється мінімум на відрізку .

Усі запити наведено в таблиці:

\boldsymbol{u_i}

\boldsymbol{v_i}

відрізок

\boldsymbol{ans_i}

Останній запит має параметри u_8 = 5, v_8 = 3, а його відповідь ans_8 = 1565158. Саме ці значення й виводяться. Зауважте, що виводяться вихідні значення u_8 та v_8, а не впорядковані межі відрізка.

Реалізація алгоритму

Елементи масиву (n \le 10^5) зберігаються, починаючи з індексу . Sparse table зберігається у масиві : mas[i][j] — це індекс мінімального елемента на відрізку [i; i + 2^j - 1]. Оскільки 2^{16} \le 10^5 < 2^{17}, значення не перевищують , тому для другого виміру достатньо LOGMAX = 17.

#define MAX 100001
#define LOGMAX 17
int a[MAX];
int mas[MAX][LOGMAX];

Функція check міняє місцями значення та , якщо u > v, щоб виконувалось u \le v. Параметри передаються за посиланням.

void check(int &u, int &v)
{
  int temp;
  if (u > v) temp = u, u = v, v = temp;
}

Функція RMQ_nlogn будує sparse table.

void RMQ_nlogn(void)
{
  int i, j;

Спочатку mas[i][0] = i для всіх .

  for (i = 1; i <= n; i++) mas[i][0] = i;

Потім для кожного , поки 2^j \le n, і для кожного такого, що відрізок [i; i + 2^j - 1] не виходить за межі масиву, обираємо з mas[i][j - 1] та mas[i + 2^{j-1}][j - 1] індекс меншого елемента.

  for (j = 1; (1 << j) <= n; j++)
    for (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];
}

Функція q повертає індекс мінімального елемента на відрізку . Обчислюємо k = \lfloor \log_2(j - i + 1) \rfloor і порівнюємо мінімуми двох відрізків довжини 2^k: [i; i + 2^k - 1] та [j - 2^k + 1; j].

int q(int i, int j)
{
  int k = (int)(log(1.0*j - i + 1) / log(2.0));
  int res = mas[i][k];
  if (a[mas[j - (1<<k) + 1][k]] < a[res])
      res = mas[j - (1<<k) + 1][k];
  return res;
}

Основна частина програми. Зчитуємо , , перший елемент масиву a_1 та параметри першого запиту і .

scanf("%d %d",&n,&m);
scanf("%d %d %d",&a[1],&u,&v);

Генеруємо елементи a_2, a_3, \ldots, a_n за формулою з умови задачі. Добуток 23 \cdot a_i не перевищує 23 \cdot 16714589 \approx 3.8 \cdot 10^8 і вміщується в тип .

for(i = 2; i <= n; i++)
  a[i] = (23 * a[i-1] + 21563) % 16714589;

Будуємо sparse table .

RMQ_nlogn();

Обробляємо запитів по черзі.

for (i = 1; i <= m; i++)
{

Копіюємо межі поточного запиту у та і, якщо потрібно, впорядковуємо їх за допомогою функції check. Самі значення та не змінюються: з них обчислюються параметри наступного запиту, і саме вони виводяться у відповіді.

  u1 = u; v1 = v; check(u1,v1);

Знаходимо мінімальний елемент на відрізку [u1; v1].

  ans = a[q(u1,v1)];

Якщо запит не останній, обчислюємо параметри наступного запиту за формулами з умови задачі.

  if (i < m)
  {
    u = ((17*u + 751 + ans + 2*i) % n) + 1;
    v = ((13*v + 593 + ans + 5*i) % n) + 1;
  }
}

Виводимо параметри останнього запиту та його відповідь.

printf("%d %d %d\n",u,v,ans);