Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions doc/topic2/FenwickTree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Дерево Фенвика (Fenwick Tree, Binary Indexed Tree)

**Дерево Фенвика** — это структура данных, которая позволяет эффективно вычислять **префиксные суммы** (суммы элементов массива от начала до заданной позиции) и поддерживать **обновление элементов** за время **O(log n)**. Оно требует **O(n)** памяти.

Пусть дан массив `a` длины `n`. Деревом Фенвика называется массив `t` той же длины, определённый следующим образом:

\[ t_i = \sum_{k=F(i)}^{i} a_k \]

где `F` — некоторая функция, удовлетворяющая условию \( F(i) \leq i \). Конкретное определение функции `F` будет дано позже.

### Основные операции:
1. **Запрос суммы (prefix sum)** — получение суммы элементов от начала массива до позиции `i`.
2. **Обновление элемента** — изменение значения элемента и корректировка структуры.

## Запрос суммы

Для вычисления суммы на отрезке `[l, r]` запрос сводится к двум суммам на префиксе:
\[ \text{sum}(l, r) = \text{sum}(r) - \text{sum}(l - 1) \]

Каждая из этих сумм вычисляется рекурсивно:
\[ \text{sum}(k) = t_k + \text{sum}(F(k) - 1) \]

## Запрос обновления

При изменении элемента `a[k]` необходимо обновить все ячейки `t_i`, в которых учтён этот элемент.

Функцию `F` можно выбрать таким образом, что количество операций при подсчёте суммы и обновлении будет \( O(\log n) \). Распространены два варианта функции `F`:

1. \( F_1(x) = x \& (x + 1) \)
2. \( F_2(x) = x - (x \& -x) + 1 \)

Первый вариант часто встречается в учебных материалах, таких как Викиконспекты и Емакс, и поэтому более известен. Второй вариант проще для запоминания и реализации, а также обладает большей гибкостью — например, позволяет выполнять бинарный поиск по префиксным суммам. Именно второй вариант будет использоваться в дальнейшем.

## Преимущества:
- Простота реализации.
- Эффективность: обе операции работают за **O(log n)**.
- Меньшая константа по сравнению с деревом отрезков.

## Недостатки:
- Менее гибкое, чем дерево отрезков (например, не поддерживает все виды запросов, которые можно делать в дереве отрезков).

---

## Принцип работы

Дерево Фенвика использует идею **бинарного представления индексов**. Каждый элемент дерева хранит сумму некоторого диапазона исходного массива.

## Пример для массива из 8 элементов:
```
Дерево Фенвика:
F[1] = A[1]
F[2] = A[1] + A[2]
F[3] = A[3]
F[4] = A[1] + A[2] + A[3] + A[4]
F[5] = A[5]
F[6] = A[5] + A[6]
F[7] = A[7]
F[8] = A[1] + A[2] + ... + A[8]
```

### Пример кода на C++

```cpp
#include <iostream>
#include <vector>

class FenwickTree {
private:
std::vector<int> tree;

public:
FenwickTree(int size) : tree(size + 1, 0) {}

// Обновление элемента: добавляем delta к A[pos]
void update(int pos, int delta) {
for (; pos < tree.size(); pos += pos & -pos) {
tree[pos] += delta;
}
}

// Запрос суммы: сумма элементов от A[1] до A[pos]
int query(int pos) {
int sum = 0;
for (; pos > 0; pos -= pos & -pos) {
sum += tree[pos];
}
return sum;
}

// Запрос суммы на отрезке [l, r]
int rangeQuery(int l, int r) {
return query(r) - query(l - 1);
}
};

int main() {
std::vector<int> arr = {0, 1, 2, 3, 4, 5, 6, 7, 8}; // Индексация с 1 для удобства

FenwickTree fenwick(arr.size() - 1); // Игнорируем 0-й элемент

// Построение дерева
for (int i = 1; i < arr.size(); ++i) {
fenwick.update(i, arr[i]);
}

// Запрос суммы первых 5 элементов
std::cout << "Sum of first 5 elements: " << fenwick.query(5) << std::endl; // 1+2+3+4+5 = 15

// Запрос суммы на отрезке [2, 5]
std::cout << "Sum from 2 to 5: " << fenwick.rangeQuery(2, 5) << std::endl; // 2+3+4+5 = 14

// Обновление элемента A[3] += 10
fenwick.update(3, 10);

// Проверка суммы после обновления
std::cout << "Sum of first 5 after update: " << fenwick.query(5) << std::endl; // 15 + 10 = 25

return 0;
}
```

#### Вывод программы:
```
Sum of first 5 elements: 15
Sum from 2 to 5: 14
Sum of first 5 after update: 25
```

### Применение
Дерево Фенвика часто используется в задачах:
- Подсчет суммы на префиксе или отрезке.
- Решение задач на инверсии в перестановках.
- Оптимизация динамического программирования.

Это мощный инструмент для задач, где важна скорость работы с префиксными суммами