diff --git a/doc/topic2/FenwickTree.md b/doc/topic2/FenwickTree.md new file mode 100644 index 0000000..dd8d147 --- /dev/null +++ b/doc/topic2/FenwickTree.md @@ -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 +#include + +class FenwickTree { +private: + std::vector 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 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 +``` + +### Применение +Дерево Фенвика часто используется в задачах: +- Подсчет суммы на префиксе или отрезке. +- Решение задач на инверсии в перестановках. +- Оптимизация динамического программирования. + +Это мощный инструмент для задач, где важна скорость работы с префиксными суммами \ No newline at end of file