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

Материал из Олимпиадное программирование в УлГТУ
Перейти к навигации Перейти к поиску
Нет описания правки
Нет описания правки
Строка 33: Строка 33:
  }
  }


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


Если в задаче требуется производить изменения на отрезке и возвращать значения отдельных элементов, то пусть массив <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>add[]</tt>, <tt>i</tt>-й элемент которого хранит значение, добавляемое ко всем элементам массива <tt>a[]</tt> на отрезке <tt>f(i)..i</tt>.


== Запрос на отрезке и модификация на отрезке ==
int sum(int r) {
    int res = 0;
    for (int i = r; i > 0; i = (i & (i + 1)) - 1)
        res += b[i] + add[i] * (i - (i & (i + 1)) + 1);
    for (int i = (r | (r + 1)); i < MAX_SIZE; i |= i + 1)
        res += add[i] * (i - (i & (i + 1)) + 1);
    return res;
}
int sum(int l, int r) {
    return sum(r) - sum(l - 1);
}
void add(int r, int val) {
    for (int i = r; i > 0; i = (i & (i + 1)) - 1)
        add[i] += val;
    for (int i = (r | (r + 1)); i < MAX_SIZE; i |= i + 1)
        b[i] += val * (i - (i & (i + 1)) + 1);
}
void add(int l, int r, int val) {
    add(r, val);
    add(l - 1, -val);
}


Если в задаче требуется и подсчитывать сумму, и производить прибавление на отрезке, то поступим следующим образом. Во-первых, пусть имеют место все соображения, изложенные в предыдущем абзаце. Далее, пусть сумма на отрезке <tt>1..r</tt> выражается как <tt><b>a[r] * r - a1[r]</b></tt>. Осталось понять, как будут изменяться элементы <tt>a1[]</tt> при обновлениях на отрезках <tt>a[]</tt>.
Также возможен другой подход к реализации обновления на отрезке; он описан в подстатье [[Дерево Фенвика с модификацией на отрезке]].  
Пусть ко всем элементам <tt>a[]</tt> на отрезке <tt>l..r</tt> добавлено значение <tt>val</tt>. Рассмотрим значение суммы элементов <tt>a[]</tt> на отрезке <tt>1..pos</tt>:
* Если <tt>pos < l</tt>, то реальная сумма не изменилась, а сумма, рассчитываемая по указанной выше формуле, также не изменилась и соответствует реальной;
* Если <tt>l <= pos <= r</tt>, то к реальной сумме добавится <tt>(val * (p - l + 1))</tt>, а к сумме, рассчитываемой по указанной ранее формуле, &mdash; <tt>(val * pos)</tt>. Чтобы расчётное значение соответствовало реальному, ко всем элементам <tt>a1[]</tt> с индексами не меньше <tt>l</tt> нужно добавить <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>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>
* Подсчёт суммы на отрезке <tt>1..r</tt>: <tt>sumA(r) * r - sumA1(r);</tt>


== Ссылки на задачи ==
== Ссылки на задачи ==
Строка 55: Строка 68:
* [http://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%B0%D1%8F_%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B8_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D1%8B_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85#.D0.94.D0.B5.D1.80.D0.B5.D0.B2.D0.BE_.D0.A4.D0.B5.D0.BD.D0.B2.D0.B8.D0.BA.D0.B0 neerc.ifmo.ru/wiki &mdash; Дерево Фенвика]
* [http://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B8%D1%81%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%B0%D1%8F_%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%D0%B8_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D1%8B_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85#.D0.94.D0.B5.D1.80.D0.B5.D0.B2.D0.BE_.D0.A4.D0.B5.D0.BD.D0.B2.D0.B8.D0.BA.D0.B0 neerc.ifmo.ru/wiki &mdash; Дерево Фенвика]
* [http://cppalgo.blogspot.ru/2011/02/fenwick-tree.html cppalgo.blogspot.com &mdash; Дерево Фенвика]
* [http://cppalgo.blogspot.ru/2011/02/fenwick-tree.html cppalgo.blogspot.com &mdash; Дерево Фенвика]
* [http://kartikkukreja.wordpress.com/2013/12/02/range-updates-with-bit-fenwick-tree kartikkukreja.wordpress.com &mdash; Range updates with BIT / Fenwick Tree]
* [http://habrahabr.ru/post/170933/ habrahabr.ru &mdash; Дерево Фенвика с модификацией на отрезке]
* [http://informatics.mccme.ru/course/view.php?id=18 informatics.mccme.ru &mdash; Курс &laquo;Структуры данных&raquo; &mdash; часть 6]
* [http://informatics.mccme.ru/course/view.php?id=18 informatics.mccme.ru &mdash; Курс &laquo;Структуры данных&raquo; &mdash; часть 6]
* [http://ejudge.btty.su/bmstu/addon/docs/articles/sbory2006-fenvik.pdf Лахно А. Дерево Фенвика]
* [http://ejudge.btty.su/bmstu/addon/docs/articles/sbory2006-fenvik.pdf Лахно А. Дерево Фенвика]

Версия от 02:01, 30 сентября 2014

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

Пусть каждый элемент массива b[] хранит частичную сумму элементов массива a[], а именно b[i] = a[f(i)] + a[f(i) + 1] + ... + a[i - 1] + a[i], где f() — определённая функция.

Тогда сумма от 1 до r вычисляется следующим образом:

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

Пусть f(i) = i & (i + 1):

  i      = xx...x100...011...1 
f(i)     = xx...x100...000...0
f(i) - 1 = xx...x011...111...1

Можно видеть, что каждая операция i = (i & (i + 1)) - 1 будет обнулять в i один единичный разряд, что обеспечит логарифмическую сложность операции суммы.

Если некоторый элемент a[i] изменился, то должны таким же образом измениться все элементы b[j], где f(j) <= i <= j. После некоторых вычислений можно убедиться, что данным ограничениям удовлетворяет индекс i, а также все индексы, получаемые из i последовательностью изменений младшего нулевого разряда на единичный, то есть применений операции i |= (i + 1).

int sum(int r) {
    int res = 0;
    for (int i = r; i > 0; i = (i & (i + 1)) - 1)
        res += b[i];
    return res;
}
int sum(int l, int r) {
    return sum(r) - sum(l - 1);
}
void add(int pos, int val) {
    for (int i = pos; i < MAX_SIZE; i |= i + 1)
       b[i] += val;
}

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

Если в задаче требуется и подсчитывать сумму, и производить прибавление на отрезке, то добавим массив add[], i-й элемент которого хранит значение, добавляемое ко всем элементам массива a[] на отрезке f(i)..i.

int sum(int r) {
    int res = 0;
    for (int i = r; i > 0; i = (i & (i + 1)) - 1)
        res += b[i] + add[i] * (i - (i & (i + 1)) + 1);
    for (int i = (r | (r + 1)); i < MAX_SIZE; i |= i + 1)
        res += add[i] * (i - (i & (i + 1)) + 1);
    return res;
}
int sum(int l, int r) {
    return sum(r) - sum(l - 1);
}
void add(int r, int val) {
    for (int i = r; i > 0; i = (i & (i + 1)) - 1)
       add[i] += val;
    for (int i = (r | (r + 1)); i < MAX_SIZE; i |= i + 1)
       b[i] += val * (i - (i & (i + 1)) + 1);
}
void add(int l, int r, int val) {
    add(r, val);
    add(l - 1, -val);
}

Также возможен другой подход к реализации обновления на отрезке; он описан в подстатье Дерево Фенвика с модификацией на отрезке.

Ссылки на задачи

Ссылки