1. Постановка задачи
Имеется массив. Необходимо уметь выполнять запросы двух видов:
- Модификация — прибавить d к каждому элементу отрезка [l, r]
- Сумма — вычислить сумму элементов отрезка [l, r]
2. Описание решения
Нетрудно заметить, что оба вида запросов можно «облегчить» до отрезка [0, r], разбивая отрезок [l, r] на два префикса. Как и для дерева отрезков, заведём второй массив, в котором будем хранить сколько надо прибавить ко всем числам этого отрезка.
Создадим класс Fenwick с прототипами будущих методов.
class Fenwick{ int *m, *mt, N; public: Fenwick(int n); Fenwick(int a[], int n); void add_range(int r, int d); void add_range(int l, int r, int d); int sum(int r); int sum(int l, int r); };
В первом конструкторе достаточно выделить память и обнулить элементы массива. А во втором ещё пройдёмся по массиву a и прорелаксируем значения в дереве.
Fenwick::Fenwick(int n){ N = n; m = new int[N]; mt = new int[N]; memset(m, 0, sizeof(int)*N); memset(mt, 0, sizeof(int)*N); } Fenwick::Fenwick(int a[], int n){ N = n; m = new int[N]; mt = new int[N]; memset(m, 0, sizeof(int)*N); memset(mt, 0, sizeof(int)*N); for(int i=0;i<N;++i){ m[i] += a[i]; if((i|(i+1))<N) m[i|(i+1)] += m[i]; } }
Рассмотрим теперь операцию изменения. Предположим пришёл запрос «прибавить 1 к элементам отрезка [0, 9]». Этот отрезок полностью покрывается непересекающимися отрезками [0, 7] и [8, 9], поэтому в 7 и 9 элементы массива mt прибавляем 1. Но есть отрезки, содержащие [0, 9] (или его часть) в качестве подотрезка. Все они располагаются справа. Для них необходимо изменить значение в массиве m на столько, сколько элементов отрезка [0, 9] они содержат. То есть в m[11] прибавить 2, а к m[15] — 10.
Из рисунка видно, что перемещения происходят по тем же законам, что и в тривиальной реализации дерева Фенвика, поэтому сразу перейдём к коду.
void Fenwick::add_range(int r, int d){ if(r<0) return ; for(int i=r;i>=0;i=(i&(i+1))-1) mt[i] += d; for(int i=r|(r+1);i<N;i|=i+1) m[i] += d*(r-(i&(i+1))+1); } void Fenwick::add_range(int l, int r, int d){ add_range(r, d); add_range(l-1, -d); }
Для суммы на префиксе подойдёт та же картинка с тем лишь пояснением, что для зелёных элементов необходимо прибавить и m, и mt, а для синих только mt (то есть учесть большие накрывающие отрезки).
int Fenwick::sum(int r){ if(r<0) return 0; int res = 0; for(int i=r;i>=0;i=(i&(i+1))-1) res += m[i] + mt[i]*(i-(i&(i+1))+1); for(int i=r|(r+1);i<N;i|=i+1) res += mt[i]*(r-(i&(i+1))+1); return res; } int Fenwick::sum(int l, int r){ return sum(r) - sum(l-1); }
3. Итоги
Нам удалось получить структуру данных с инициализацией за O(N), модификацией и суммой на отрезке за O(logN). На рандомном тесте с 10000000 элементов и 10000000 запросов получил такие цифры:
Структура данных | Инициализация | Модификация | Сумма |
---|---|---|---|
Fenwick | 0.13 с | 9.12 с | 8.90 с |
RSQ (реализация с e-maxx) | 0.25 с | 17.13 с | 13.53 с |
ссылка на оригинал статьи http://habrahabr.ru/post/170933/
Добавить комментарий