НЯ!
Эта статья полна любви и обожания.
Возможно, стоит добавить ещё больше?
Дерево Фенвика или двоичное индексированное дерево (англ. binary indexed tree) — структура данных, которая на многих задачах заменяет собой дерево отрезков, но при этом работает в 3-4 раза быстрее, занимает минимально возможное количество памяти (столько же, сколько и массив той же длины), намного быстрее пишется и легче обобщается на большие размерности.
Пусть дан массив
где
Запрос суммы. Когда нам нужна сумма на отрезке, мы будем сводить этот запрос к двум суммам на префиксе:
Запрос обновления. Когда мы изменяем
x & (x + 1)x - (x & -x) + 1Первый вариант описан на Викиконспектах и Емаксе и поэтому более известен. Второй, как мы дальше увидим, более простой для запоминания и кодинга, а так же более гибкий — например, там можно делать бинпоиск по префиксным суммам. Его мы и будем использовать.
Примечание. Наверное, меньше четверти умеющих писать эту структуру полностью понимают, как она работает. Анализ действительно весьма сложный, поэтому мы приведём его в конце. Рекумендуется пока что абстрагироваться и принять на веру, что любой префикс разбивается на

Так как
int t[maxn];
// возвращает сумму на префиксе
int sum (int r) {
int res = 0;
for (; r > 0; r -= r & -r)
res += t[r];
return res;
}
int sum (int l, int r) {
return sum(r) - sum(l-1);
}
// обновляет нужные t
void add (int k, int x) {
for (; k <= n; k += k & -k)
t[k] += x;
}Автор отмечает красивую симметрию в формулах r -= r & -r и k += k & -k, которой нет в «традиционной» версии.
-мерное дерево Фенвика пишется в строчку
Нужно добавить всего одну такую же строчку в sum, add, а также при подсчете суммы на прямоугольнике вместо двух запросов к префиксным суммам использовать четыре.
sum перепишется следующим образом:
int sum (int r1, int r2) {
int res = 0;
for (int i = r1; i > 0; i -= i & -i)
for (int j = r2; j > 0; j -= j & -j)
ans += t[i][j];
return res;
}В
Если размерности больше, чем позволяет память, то можно вместо массива t использовать хэш-таблицу — так потенциально потребуется
Оказывается, можно производить бинарный поиск (точнее, спуск) по префиксным суммам за
// возвращает индекс, на котором сумма уже больше
int lower_bound (int s) {
int k = 0;
for (int l = logn; l >= 0; l--) {
if (k + (1<<l) <= n && t[k + (1<<l)] < s) {
k += (1<<l);
s -= t[k];
}
}
return k;
}Если знать, что
Отметим, что в «традиционной» индексации такое делать нельзя.
Дерево Фенвика можно использовать, когда наша операция обратима, а также когда трюк с префиксными суммами работает. Это обычно простые операции типа суммы, xor, умножения по модулю (если гарантируется, что на этот модуль ничего не делится). Минимум и gcd, отложенные операции и персистентность прикрутить в общем случае уже не получится — тогда уже нужно писать дерево отрезков.
Итак, мы выбрали вариант с x - (x & -x) + 1. Поймем, что означает x & -x.
Лемма. x & -x возвращает последний единичный бит в двоичной записи x.
Доказательство потребует знания, как в компьютерах хранятся целые числа. Чтобы процессор не сжигал лишние такты, проверяя знак числа при арифметических операциях, их хранят как бы по модулю
Как будет выглядеть -x в битовой записи? Ответ можно мысленно разделить на три блока:
Пример:
Теперь мы можем доказать нашу лемму. Когда мы сделаем &, в префиксе до младшего единичного бита все биты x и -x будут противоположными, младший единичный бит останется единичным, а на суффиксе все как было нулями, так и осталось. Следовательно, «выживет» только этот самый младший единичный бит, что мы и доказывали.
Следствие 1. sum будет работать за логарифм, а точнее за количество единичных битов в записи x -= x & -x, то есть удаляем младший бит.
Следствие 2. add тоже будет работать за логарифм: каждую итерацию количество нулей на конце
Следствие 3. (Почему дерево Фенвика — дерево.)
Длина отрезка, соответствующего любому
То есть, если напрячь воображение, то
Теперь единственное, что осталось доказать — это корректность add. На самом деле, в add мы делаем ни что иное, как подъём от вершины до корня по всем предкам.
Как для y >= x > y - (y & -y).
Дальше читателю предлагается самостоятельно попялиться в пример, чтобы понять, что x + (x & -x) — минимальное такое число: