Створимо дерев відрізків для підрахунку сум — по одному для кожної літери від '' до ''. Кожне дерево повинно підтримувати множинні модифікації.
Наприклад, для літери '' та рядка "" відповідне дерево відрізків буде виглядати так:
За допомогою такого дерева можна за логарифмічний час визначити, скільки разів задана буква зустрічається на відрізку [l; r].
Виконання запиту на відрізку [l; r] зводиться до сортування підрахунком. Спочатку в масиві підраховується, скільки разів кожна буква зустрічається на даному відрізку.
Далі, залежно від значення , ми "перебудовуємо" цю ділянку рядка:
якщо k = 1, то символи розташовуються в невисхідному порядку — тобто літери від '' до '';
якщо k = 0, то у спадному порядку, від '' до ''.
Для цього на всьому відрізку [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);