Иногда запросы на отрезках (и не только - круг применения техники куда шире) невозможно решить деревом отрезков никак. Тогда на помощь приходит корневая декомпозиция (sqrt-декомпозиция).
Особенность заключается в том, что мы можем все-таки перебрать некоторые элементы в лоб, пересчитать что-то и т.п.
Основная идея декомпозиции заключается в том, что если разбить \(n\) элементов на блоки по \(\sqrt{n}\) элементов, то всего блоков тоже будет \(\sqrt{n}\).
Таким образом, мы можем делать за одну итерацию:
Простейшая задача - сумма на отрезке \([l;r]\) без запросов изменений.
Первый блок - первые \(\sqrt{n}\) элементов массива, второй - следующие и т.п. \(i\)-й элемент принадлежит \( \lfloor \frac{i}{\sqrt{n}}\rfloor\)-му блоку. Предподсчитаем сумму каждого блока.
Когда приходит запрос - смотрим на все блоки что целиком входят в границы отрезка запроса и суммируем их. Далее проверяем все элменты что не вошли в блоки. Это какой-то суффикс \( \lfloor \frac{l}{\sqrt{n}} \rfloor \)-го блока и префикс \( \lfloor \frac{r}{\sqrt{n}} \rfloor \)-го. Их можно перебрать, так как суммарно их меньше чем \(2\sqrt{n}\).
Итого: \(O(\sqrt{n})\) на запрос. Легко решается и задача, с обновлением в точке: просто пересчитать один блок.
int a[n];
int C = sqrt(n); //размер блока
int blocks[n/C+1];//сумма блоков
for(int i = 0; i < n; i++)
blocks[i/C]+=a[i];
//ответ на запрос:
int l,r; // границы отрезка
int sum=0;
if(l/C==r/C) //отдельно обработываем этот случай
for(int i = l;i <= r; i++)
sum+=a[i];
else
{
// считаем те блоки, что полностью внутри запроса
for(int i = 1+l/C; i < r/C; i++)
sum+=blocks[i];
// считаем сумму хвостов:
for(int i = l; i/C==l/C; i++)
sum+=a[i];
for(int j = (r/C)*C; i <= r; i++)
sum+=a[i];
}
// sum - ответ
Вообще размер блока \(\sqrt{n}\) - асимптотически лучший вариант. Но часто используют размер блока, как какую-то степень двойки, так как дорогостоящее деление будет происходить быстрее.
Допустим у нас добавился запрос прибавления на отрезке.
Когда будет приходить такой запрос, мы на самом деле честно прибавим только для "хвостов" (обновляя сумму блока), а для остальных блоков мы запишем для каждого блока в \(lazy\), что мы прибавили им какое-то значение.
Во время вычисления суммы на отрезке необходимо учитывать факт того, что к некоторым блокам мы прибавляли, то есть, кроме \(blocks[i]\), к ответу прибавим еще \(lazy[i]*C\).