Дерево Фенвика с модификацией на отрезке: различия между версиями

Материал из Олимпиадное программирование в УлГТУ
Перейти к навигации Перейти к поиску
Нет описания правки
Строка 1: Строка 1:
Переименуем функцию <tt>sum()</tt> в функцию <tt>alpha()</tt>, а функцию <tt>add()</tt> &mdash; в функцию <tt>modifyAlphas()</tt>.
int alpha(int r) {
    int res = 0;
    for (int i = r; i >= 0; i = (i & (i + 1)) - 1)
        res += b[i];
    return res;
}
void modifyAlphas(int pos, int val) {
    for (int i = pos; i < MAX_SIZE; i |= i + 1)
        b[i] += val;
}
Тогда в общем случае мы можем считать, что вызов <tt>alpha(i)</tt> возвращает результат некоторой функции <tt>&alpha;(i)</tt> (или элемент некоторого массива <tt>&alpha;[i]</tt>), а вызов <tt>modifyAlphas(pos, val)</tt> добавляет значение <tt>val</tt> ко всем <tt>&alpha;(i)</tt> (или <tt>&alpha;[i]</tt>), у которых <tt>i >= pos</tt>.
В зависимости от того, как трактовать значение <tt>&alpha;(i)</tt>, можно получить несколько применений дерева Фенвика.
Если считать, что <tt>&alpha;(i)</tt> &mdash; сумма элементов массива <tt>a[]</tt> от <tt>a[0]</tt> до <tt>a[i]</tt>, то мы получим базовую версию дерева Фенвика, в которой функция <tt>modifyAlphas()</tt> изменяет элемент массива <tt>a[]</tt>.
== Запрос отдельных элементов и модификация на отрезке ==
== Запрос отдельных элементов и модификация на отрезке ==


Если в задаче требуется производить изменения на отрезке и возвращать значения отдельных элементов, то пусть массив <tt>a[]</tt> хранит не сами элементы, а разности текущего и предыдущего элементов. Тогда, чтобы увеличить все элементы на отрезке <tt>l..r</tt> на величину <tt>val</tt>, достаточно добавить <tt>val</tt> к <tt>a[l]</tt> и добавить <tt>-val</tt> к <tt>a[r + 1]</tt> (две операции <tt>add</tt>), а чтобы вывести фактическое значение элемента в позиции <tt>pos</tt>, достаточно вернуть сумму <tt>a[1] + a[2] + ... + a[pos]</tt> (операция <tt>sum</tt>).
Пусть <tt>&alpha;(i)</tt> &mdash; значение <tt>i</tt>-го элемента массива <tt>a[]</tt>. Тогда вызов <tt>modifyAlphas(pos, val)</tt> добавит <tt>val</tt> ко всем элементам <tt>a[]</tt>, у которых <tt>i >= pos</tt>. Следовательно, чтобы увеличить только элементы с индексами от <tt>l</tt> до <tt>r</tt>, нужно вызвать <tt>modifyAlphas(l, val)</tt> и <tt>modifyAlphas(r + 1, -val)</tt>.
 
int get(int pos) {
    return alpha(pos);
}
int add(int l, int r, int val) {
    modifyAlphas(l, val);
    modifyAlphas(r + 1, -val);
}


== Запрос на отрезке и модификация на отрезке ==
== Запрос на отрезке и модификация на отрезке ==


Если в задаче требуется и подсчитывать сумму, и производить прибавление на отрезке, то поступим следующим образом. Во-первых, пусть имеют место все соображения, изложенные в предыдущем абзаце. Далее, пусть сумма на отрезке <tt>1..r</tt> выражается как <tt><b>a[r] * r - a1[r]</b></tt>. Осталось понять, как будут изменяться элементы <tt>a1[]</tt> при обновлениях на отрезках <tt>a[]</tt>.
Пусть используется второе дерево Фенвика, реализующее функции <tt>beta()</tt> и <tt>modifyBetas()</tt>, и пусть <tt>(&beta;(i) * i + &alpha;(i))</tt> &mdash; сумма элементов массива <tt>a[]</tt> от <tt>a[0]</tt> до <tt>a[i]</tt>.
Пусть ко всем элементам <tt>a[]</tt> на отрезке <tt>l..r</tt> добавлено значение <tt>val</tt>. Рассмотрим значение суммы элементов <tt>a[]</tt> на отрезке <tt>1..pos</tt>:
Далее, пусть элементы массива <tt>a[]</tt> с индексами от <tt>l</tt> до <tt>r</tt> увеличились на величину <tt>val</tt>. Рассмотрим, как при этом изменится сумма элементов массива <tt>a[]</tt> от <tt>a[0]</tt> до <tt>a[i]</tt>:
* Если <tt>pos < l</tt>, то реальная сумма не изменилась, а сумма, рассчитываемая по указанной выше формуле, также не изменилась и соответствует реальной;
* Если <tt>i < l</tt>, то сумма не изменилась, и значение <tt>(&beta;(i) * i + &alpha;(i))</tt> ей соответствует.
* Если <tt>l <= pos <= r</tt>, то к реальной сумме добавится <tt>(val * (pos - l + 1))</tt>, а к сумме, рассчитываемой по указанной ранее формуле, &mdash; <tt>(val * pos)</tt>. Чтобы расчётное значение соответствовало реальному, ко всем элементам <tt>a1[]</tt> с индексами не меньше <tt>l</tt> нужно добавить <tt>(val * (l - 1))</tt>;
* Если <tt>l <= i <= r</tt>, то сумма увеличилась на <tt>(val * (i - l + 1))</tt>. Следовательно, к <tt>&beta;(i)</tt> нужно добавить <tt>val</tt>, а к <tt>&alpha;(i)</tt> &mdash; <tt>(val * (-l + 1))</tt>.
* Если <tt>r < pos</tt>, то к реальной сумме добавится <tt>(val * (r - l + 1))</tt>, а к сумме, рассчитываемой по указанной ранее формуле, &mdash; ничего. Чтобы расчётное значение соответствовало реальному, от всех элементов <tt>a1[]</tt> с индексами больше <tt>r</tt> нужно отнять <tt>(val * (r - l + 1))</tt>, то есть прибавить к ним <tt>(val * (l - 1 - r))</tt>;
* Если <tt>i > r</tt>, то сумма увеличилась на <tt>(val * (r - l + 1))</tt>. Следовательно, <tt>&beta;(i)</tt> изменять не нужно, а к <tt>&alpha;(i)</tt> нужно добавить <tt>(val * (r - l + 1))</tt>.
Можно видеть, что операции над массивом <tt>a1[]</tt> можно реализовать с помощью второго дерева Фенвика:
 
* Добавление значения <tt>val</tt> на отрезке <tt>l..r</tt>: <tt>updateA(l, val); updateA(r + 1, -val); updateA1(l, val * (l - 1)); updateA1(r + 1, -val * r);</tt>
int sum(int r) {
* Подсчёт суммы на отрезке <tt>1..r</tt>: <tt>sumA(r) * r - sumA1(r);</tt>
    return beta(r) * r + alpha(r);
}
void add(int l, int r, int val) {
    modifyBetas(l, val);
    modifyAlphas(l, val * (-l + 1));
    modifyBetas(r + 1, -val);    //modifyBetas(r + 1, 0 - val);
    modifyAlphas(r + 1, val * r); //modifyAlphas(r + 1, (val * (r - l + 1)) - (val * (-l + 1)));
}


== Ссылки ==
== Ссылки ==
* [http://petr-mitrichev.blogspot.ru/2013/05/fenwick-tree-range-updates.html petr-mitrichev.blogspot.ru &mdash; Fenwick tree range updates]
* [http://apps.topcoder.com/forums/?module=Thread&threadID=756271 topcoder.com &mdash; How to modify BIT to update the ranges and query the ranges?]
* [http://apps.topcoder.com/forums/?module=Thread&threadID=756271 topcoder.com &mdash; How to modify BIT to update the ranges and query the ranges?]
* [http://apps.topcoder.com/forums/?module=Thread&threadID=715842 topcoder.com &mdash; Range update in BIT]
* [http://apps.topcoder.com/forums/?module=Thread&threadID=715842 topcoder.com &mdash; Range update in BIT]

Версия от 02:03, 14 ноября 2014

Переименуем функцию sum() в функцию alpha(), а функцию add() — в функцию modifyAlphas().

int alpha(int r) {
    int res = 0;
    for (int i = r; i >= 0; i = (i & (i + 1)) - 1)
        res += b[i];
    return res;
}

void modifyAlphas(int pos, int val) {
    for (int i = pos; i < MAX_SIZE; i |= i + 1)
        b[i] += val;
}

Тогда в общем случае мы можем считать, что вызов alpha(i) возвращает результат некоторой функции α(i) (или элемент некоторого массива α[i]), а вызов modifyAlphas(pos, val) добавляет значение val ко всем α(i) (или α[i]), у которых i >= pos. В зависимости от того, как трактовать значение α(i), можно получить несколько применений дерева Фенвика. Если считать, что α(i) — сумма элементов массива a[] от a[0] до a[i], то мы получим базовую версию дерева Фенвика, в которой функция modifyAlphas() изменяет элемент массива a[].

Запрос отдельных элементов и модификация на отрезке

Пусть α(i) — значение i-го элемента массива a[]. Тогда вызов modifyAlphas(pos, val) добавит val ко всем элементам a[], у которых i >= pos. Следовательно, чтобы увеличить только элементы с индексами от l до r, нужно вызвать modifyAlphas(l, val) и modifyAlphas(r + 1, -val).

int get(int pos) {
    return alpha(pos);
}

int add(int l, int r, int val) {
    modifyAlphas(l, val);
    modifyAlphas(r + 1, -val);
}

Запрос на отрезке и модификация на отрезке

Пусть используется второе дерево Фенвика, реализующее функции beta() и modifyBetas(), и пусть (β(i) * i + α(i)) — сумма элементов массива a[] от a[0] до a[i]. Далее, пусть элементы массива a[] с индексами от l до r увеличились на величину val. Рассмотрим, как при этом изменится сумма элементов массива a[] от a[0] до a[i]:

  • Если i < l, то сумма не изменилась, и значение (β(i) * i + α(i)) ей соответствует.
  • Если l <= i <= r, то сумма увеличилась на (val * (i - l + 1)). Следовательно, к β(i) нужно добавить val, а к α(i)(val * (-l + 1)).
  • Если i > r, то сумма увеличилась на (val * (r - l + 1)). Следовательно, β(i) изменять не нужно, а к α(i) нужно добавить (val * (r - l + 1)).
int sum(int r) {
    return beta(r) * r + alpha(r);
}

void add(int l, int r, int val) {
    modifyBetas(l, val);
    modifyAlphas(l, val * (-l + 1));
    modifyBetas(r + 1, -val);     //modifyBetas(r + 1, 0 - val);
    modifyAlphas(r + 1, val * r); //modifyAlphas(r + 1, (val * (r - l + 1)) - (val * (-l + 1)));
}

Ссылки