Алгоритм Merge tree. У кожній вершині дерева відрізків будемо зберігати додатковий масив. Вершина, що відповідає фундаментальному відрізку , містить у своєму масиві відсортований набір чисел a_i, ..., a_j.
Побудова такого дерева займає O(nlog_2n) часу, оскільки при об'єднанні двох дочірніх вершин їхні відсортовані масиви зливаються аналогічно кроку алгоритму злиття в сортуванні злиттям.
Щоб знайти кількість чисел на відрізку [l, r], які менші за , потрібно розбити цей відрізок на набір фундаментальних відрізків дерева відрізків. Для кожного з них виконується бінарний пошук у відповідному відсортованому масиві, що дозволяє визначити кількість елементів, менших за .
Оскільки відрізок [l, r] розбивається не більше ніж на O(log_2n) фундаментальних відрізків, а бінарний пошук у кожному з них виконується за O(log_2n), час обробки одного запиту становить O(log_2^2n).
Приклад
Реалізація алгоритму
Оголосимо дерево відрізків. У кожній його вершині будемо зберігати масив чисел.
vector\u003cvector\u003cint\u003e \u003e SegTree;Вхідні числа зберігаємо в масиві .
vector\u003cint\u003e a;Функція BuildTree будує дерево відрізків за масивом . Відсортований масив у кожній вершині формується шляхом злиття масивів її дочірніх вершин за час за допомогою функції merge.
void BuildTree(int v, int lpos, int rpos)
{
if (lpos == rpos)
SegTree[v].push_back(a[lpos]);
else
{
int mid = (lpos + rpos) / 2;
BuildTree(2*v, lpos, mid);
BuildTree(2*v+1, mid+1, rpos);
SegTree[v].resize(rpos - lpos + 1);
merge(SegTree[2*v].begin(), SegTree[2*v].end(),
SegTree[2*v+1].begin(), SegTree[2*v+1].end(),
SegTree[v].begin());
}
}Функція Query підраховує кількість чисел на відрізку [l, r], значення яких менші за . Для кожного фундаментального відрізка [left; right] за допомогою функції lower\_bound знаходимо кількість чисел, менших за .
int Query(int v, int lpos, int rpos, int left, int right, int x)
{
if (left \u003e right) return 0;
if ((lpos == left) \u0026\u0026 (rpos == right))
return lower_bound(SegTree[v].begin(), SegTree[v].end(), x) –
SegTree[v].begin();
int mid = (lpos + rpos) / 2;
return Query(2*v, lpos, mid, left, min(mid, right), x) +
Query(2*v+1, mid+1, rpos, max(left, mid+1), right, x);
}Основна частина програми. Читаємо вхідний масив чисел.
scanf("%d",\u0026n);
a.resize(n + 1);
for(i = 1; i \u003c= n; i++)
scanf("%d",\u0026a[i]);Будуємо дерево відрізків за масивом .
SegTree.resize(4*n);
build(1,1,n);Послідовно обробляємо запитів.
scanf("%d",\u0026q);
for(i = 0; i \u003c q; i++)
{
scanf("%d %d %d",\u0026l,\u0026r,\u0026x);
printf("%d\n",Query(1,1,n,l,r,x));
}