Просте завдання#7414

Просте завдання (Розбір)

Створимо дерев відрізків для підрахунку сум — по одному для кожної літери від '' до ''. Кожне дерево повинно підтримувати множинні модифікації.

Наприклад, для літери '' та рядка "" відповідне дерево відрізків буде виглядати так:

За допомогою такого дерева можна за логарифмічний час визначити, скільки разів задана буква зустрічається на відрізку [l; r].

Виконання запиту на відрізку [l; r] зводиться до сортування підрахунком. Спочатку в масиві підраховується, скільки разів кожна буква зустрічається на даному відрізку.

Далі, залежно від значення , ми "перебудовуємо" цю ділянку рядка:

Для цього на всьому відрізку [l; r] для кожної літери '' + , кількість якої відома з масиву cnt, ми встановлюємо в дереві відрізків відповідну кількість одиниць, починаючи з позиції (якщо сортуємо за зростанням) або з позиції (якщо за спаданням).

Наприклад, нехай на позиціях знаходиться підрядок "". Підрахуємо кількість кожної літери на цьому інтервалі:

Щоб відсортувати літери на відрізку за зростанням, необхідно для дерева відрізків кожної літери спочатку занулити значення на інтервалі , а потім виконати наступні операції:

Таке "оновлення" дерев відрізків фактично моделює перестановку символів на потрібному відрізку без безпосередньої зміни самого рядка.

Після обробки всіх запитів підсумковий рядок відновлюється з вмісту дерев відрізків: для кожної літери послідовно виконується обхід її дерева, і в тих позиціях, де значення дорівнює , в результуючий рядок записується відповідний символ.

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

Для кожної літери від '' до '' створимо власне дерево відрізків. Таким чином, всього використовується ALPHABET = 26 дерев, кожне з яких підтримує операції сумування і множинну модифікацію.

#define MAX 100010
#define ALPHABET 26
struct node
{
  int sum, add;
} SegTree[4*MAX][ALPHABET];

Оголосимо масив для підрахунку кількості входжень кожної літери.

int cnt[ALPHABET];

Побудуємо дерев відрізків по рядку — по одному для кожної літери латинського алфавіту.

void build(int v, int lpos, int rpos)
{
  if (lpos == rpos)
  {
    SegTree[v][s[lpos] - 'a'].sum = 1;
    for (int i = 0; i < ALPHABET; i++)
      SegTree[v][i].add = -1;
  }
  else
  {
    int mid = (lpos + rpos) / 2;
    build(v * 2, lpos, mid);
    build(v * 2 + 1, mid + 1, rpos);
    for (int i = 0; i < ALPHABET; i++)
    {
      SegTree[v][i].sum = 
             SegTree[v * 2][i].sum + SegTree[v * 2 + 1][i].sum;
      SegTree[v][i].add = -1;
    }
  }
}

Вершині відповідає відрізок [lpos, rpos], при цьому mid = (lpos + rpos) / 2. Виконаємо пропихання цієї вершини в дереві відрізків з номером .

void Push(int v, int lpos, int mid, int rpos, int ver)
{
  if (SegTree[v][ver].add != -1)
  {
    SegTree[2 * v][ver].add = SegTree[v][ver].add;
    SegTree[2 * v][ver].sum = (mid - lpos + 1) * SegTree[v][ver].add;
    SegTree[2 * v + 1][ver].add = SegTree[v][ver].add;
    SegTree[2 * v + 1][ver].sum = (rpos - mid) * SegTree[v][ver].add;
    SegTree[v][ver].add = -1;
  }
}

Вершині відповідає відрізок [lpos, rpos]. В дереві відрізків з номером встановимо значення для всіх елементів з індексами від до .

void SetValue(int v, int lpos, int rpos, int left, int right, 
              int val, int ver)
{
  if (left > right) return;
  if ((lpos == left) && (rpos == right))
  {
    SegTree[v][ver].add = val;
    SegTree[v][ver].sum = (right - left + 1) * val;
    return;
  }

  int mid = (lpos + rpos) / 2;
  Push(v, lpos, mid, rpos, ver);

  SetValue(2 * v, lpos, mid, left, min(mid, right), val, ver);
  SetValue(2 * v + 1, mid + 1, rpos, max(left, mid + 1), right, 
           val, ver);

  SegTree[v][ver].sum = 
         SegTree[2 * v][ver].sum + SegTree[2 * v + 1][ver].sum;
}

Вершині відповідає відрізок [lpos, rpos]. Знайдемо суму елементів на відрізку [left; right] в дереві відрізків з номером .

int Summa(int v, int lpos, int rpos, int left, int right, int ver)
{
  if (left > right) return 0;
  if ((lpos == left) && (rpos == right)) return SegTree[v][ver].sum;

  int mid = (lpos + rpos) / 2;
  Push(v, lpos, mid, rpos, ver);

  return Summa(2 * v, lpos, mid, left, min(mid, right), ver) +
    Summa(2 * v + 1, mid + 1, rpos, max(left, mid + 1), right, ver);
}

Виконаємо пропихання у всіх вершинах дерева відрізків з номером . Потім внесемо букву + '' у всі позиції рядка , де її значення в дереві відрізків дорівнює .

void get(int v, int lpos, int rpos, int ver)
{
  if (SegTree[v][ver].sum == 0) return;
  if (lpos == rpos)
  {
    s[lpos] = ver + 'a';
    return;
  }

  int mid = (lpos + rpos) / 2;
  Push(v, lpos, mid, rpos, ver);

  get(2 * v, lpos, mid, ver);
  get(2 * v + 1, mid + 1, rpos, ver);
}

Основна частина програми. Читаємо вхідні дані. За вхідним рядком будуємо дерев відрізків — по одному для кожної літери латинського алфавіту.

cin >> n >> q;
cin >> s;
s = " " + s;
build(1,1,n);

Послідовно обробляємо запитів.

for (i = 0; i < q; i++)
{
  cin >> l >> r >> command;

Виконаємо сортування підрахунком всіх літер на відрізку [l; r]. Для цього спочатку підрахуємо кількість входжень кожної літери '' + на даному інтервалі і запишемо результат у .

  for (j = 0; j < ALPHABET; j++)
    cnt[j] = Summa(1, 1, n, l, r, j);

Потім, починаючи з позиції , будемо рухатися вправо або вліво залежно від значення змінної .

  pos = (command == 1) ? l : r;
  for (j = 0; j < ALPHABET; j++)
  {
    if (!cnt[j]) continue;
    SetValue(1, 1, n, l, r, 0, j);
    if (command)
    {
      SetValue(1, 1, n, pos, pos + cnt[j] - 1, 1, j);
      pos += cnt[j];
    }
    else
    {
      SetValue(1, 1, n, pos - cnt[j] + 1, pos, 1, j);
      pos -= cnt[j];
    }
  }
}

Генерація підсумкового рядка виконується на основі вмісту дерев відрізків. Функція get(1, 1, n, i), використовуючи дані -го дерева відрізків, розставляє всі літери '' + на відповідні позиції рядка . Функція get виконує повне пропихання даних, проходячи по всіх вершинах дерева відрізків за час .

for (i = 0; i < ALPHABET; i++)
  get(1, 1, n, i);

Виводимо підсумковий рядок.

cout << s.substr(1);