Обычная разреженная таблица отвечает на общий ассоциативный запрос за O(logn), объединяя непересекающиеся блоки. Её запрос за O(1) использует пересекающиеся отрезки и требует, чтобы повторение элементов не меняло результат.
Разреженная таблица без пересечений (disjoint sparse table) объединяет два отрезка без пересечения. Поэтому она работает с любой ассоциативной операцией. Коммутативность не требуется: элементы объединяются в исходном порядке.
Индексы начинаются с 0, а интервалы запросов включают оба конца.
Построение суффиксов и префиксов
На уровне k разделим массив на блоки длины 2k+1, начиная с позиции 0. У блока, начинающегося в b, середина равна
m=b+2k.
Для каждой позиции i в левой половине храним суффикс, заканчивающийся в m−1:
tk,i=ai∘ai+1∘⋯∘am−1.
Для каждой позиции j в правой половине храним префикс, начинающийся в m:
tk,j=am∘am+1∘⋯∘aj.
Суффиксы строим справа налево, добавляя каждый новый элемент слева. Префиксы строим слева направо, добавляя каждый новый элемент справа. Это сохраняет порядок операции.
Последний блок может быть короче. Обрабатываем только существующие позиции и пропускаем блок, если его правая половина пуста.
Выбор уровня для запроса
Если l=r, вернём al.
Иначе пусть k — позиция старшего установленного бита в l⊕r:
k=⌊log2(l⊕r)⌋.
Здесь ⊕ — побитовый XOR.
Все биты выше k равны, поэтому l и r принадлежат одному блоку длины 2k+1. Их биты с номером k различаются. Так как l<r, этот бит равен 0 у l и 1 у r, то есть они лежат в левой и правой половинах.
На этом уровне tk,l покрывает [l,m−1], а tk,r покрывает [m,r]. Эти два отрезка не пересекаются и в точности покрывают запрос. Ответ равен
tk,l∘tk,r.
Сначала объединяем левый суффикс, затем правый префикс.
Реализация
Сложность
Каждый уровень обрабатывает O(n) элементов, а уровней O(logn). Предобработка занимает O(nlogn) времени, а каждый запрос — O(1) времени.
Таблица использует O(nlogn) элементов; глобальные массивы резервируют место под максимально допустимый размер. Эти оценки предполагают, что каждое хранимое значение занимает O(1) памяти, а объединение двух значений занимает O(1) времени.
Массив не меняется после предобработки.
#include <bits/stdc++.h>using namespace std;const int N = 200005;const int K = 20;const int MOD = 1000;int n;long long a[N], t[K][N];long long op(long long x, long long y){ return x * y % MOD;}void build(){ for(int k = 0;(1<< k)< n; k++){ int b = 1<< k; for(int l = 0; l < n; l += 2 * b){ int m = min(l + b, n); int r = min(l + 2 * b, n); if(m == r) continue; t[k][m - 1] = a[m - 1]; for(int i = m - 2; i >= l; i--){ t[k][i] = op(a[i], t[k][i + 1]);} t[k][m] = a[m]; for(int i = m + 1; i < r; i++){ t[k][i] = op(t[k][i - 1], a[i]);}}}}long long get(int l, int r){ if(l == r) return a[l]; int k = 31 - __builtin_clz((unsigned int)(l ^ r)); return op(t[k][l], t[k][r]);}