Некоторые теоретические сведения
Обобщённое условие задач на дерево отрезков, как правило, выглядит так:
«Пусть дан моноид [latex]\left(\mathbb{G}, \circ\right)[/latex], где [latex]\mathbb{G}[/latex] — некоторое непустое множество, [latex]\circ[/latex] — ассоциативная бинарная алгебраическая операция на этом множестве, имеющая нейтральный элемент, [latex]A[/latex] — последовательность (массив) элементов из [latex]\mathbb{G}[/latex], содержащая [latex]n[/latex] элементов ([latex]n \in \mathbb{N}[/latex]; с математической точки зрения [latex]A[/latex] — вектор, построенный из элементов [latex]\mathbb{G}[/latex], или [latex]А = \left( x_{0}, x_{1}, \ldots, x_{n-1} \right) \in \mathbb{G}^{n}[/latex]).
Даётся [latex]m[/latex] ([latex]m \in \mathbb{N}[/latex]) запросов двух типов:
1) вычислить значение выражения [latex]x_{i} \circ x_{i+1} \circ \ldots \circ x_{j-1} \circ x_{j}[/latex] с заданными [latex]i[/latex], [latex]j[/latex] ([latex]0 \le i \le j \le n-1[/latex], [latex]i, j \in \mathbb{N} \cup \{ 0 \}[/latex]) и вывести его;
2) заменить значение элемента с индексом [latex]k[/latex] на [latex]y[/latex] ([latex]k \in \mathbb{N} \cup \{ 0 \}[/latex], [latex]k \le n-1[/latex], [latex]y \in \mathbb{G}[/latex]).»
Как правило, человек, впервые увидевший задачу подобного рода, решает её следующим образом: для запросов первого типа (далее — запросы значения на отрезке [latex]\left[i, j\right][/latex]) создаётся вспомогательная переменная, изначально равная нейтральному элементу моноида (к примеру, если [latex]\left( \mathbb{G}, \circ \right) = \left( \mathbb{Z}, + \right)[/latex] то нейтральным элементом относительно [latex]+[/latex] является [latex]0[/latex]), и запускается цикл на заданном отрезке, который «прибавляет» к ней новые «слагаемые», а обработка запросов из пункта 2 реализуется через простое присваивание элементу массива с заданным индексом [latex]i[/latex] значения [latex]y[/latex]. Таким образом вычислительная сложность запросов замены составляет [latex]O\left(1\right)[/latex], а запросов поиска значения на отрезке [latex]\left[i, j\right][/latex] в лучшем случае составляет [latex]O\left(1\right)[/latex], когда [latex]i = j[/latex], а в худшем [latex]O\left(n\right)[/latex], когда [latex]i = 0[/latex], [latex]j = n-1[/latex].
Дерево отрезков — структура данных, которая позволяет сбалансировать операции замены и вычисления значения на заданном отрезке до вычислительной сложности [latex]O\left(\log_{2}{n}\right)[/latex] и значительно улучшить общую сложность программы с [latex]O\left(n+n\cdot m\right) = O\left(n\cdot m\right)[/latex] до [latex]O\left(n+m\cdot\log_{2}{n}\right)[/latex].
Определение: массив/последовательность элементов/вектор, над которым построено дерево отрезков, называется базой дерева или просто базой, а число её элементов — её размерностью.
Задача 1: единичная модификация
Написать класс «дерево отрезков», применимый к любой задаче на моноиде, в которой присутствуют запросы замены элемента и результата операции на отрезке,
и таблицу его базовых функций и параметров.
Код класса
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
#include <iostream> using namespace std; template <class TYPE> class segments_tree { TYPE *SegmentTree; size_t base_capacity = 0; TYPE (*g)(TYPE, TYPE); TYPE neutral; /* TYPE: Type of elements in the tree. SegmentTree: (pointer) Array-based tree of elements. base_capacity: (unsigned integer value) Parental array size, rounded to the closest upper power of 2. g: (pointer) Pointer to the funtion (associative binary operation), applied on the given array of elements. WARNING: The given function must be associative. The monoidal structure of the tree will be ruined otherwise. neutral: (array element) The value of element, which is neutral relatively to "g" function. ################################ #### INNER HELPER FUNCTIONS #### ################################ result_on_segment_in: returns value on segment [l, r]; */ TYPE result_on_segment_in ( size_t v, size_t tl, size_t tr, size_t l, size_t r ) { if (l > r) return neutral; else if (l == tl && r == tr) return SegmentTree[v]; else { size_t tm = (tl + tr) / 2; return g(result_on_segment_in(v*2, tl, tm, l, min(r,tm)), result_on_segment_in(v*2+1, tm+1, tr, max(l,tm+1), r)); } } /* make_monoid_and_fill_rest: - fills the unused space of array (which appears due to rounding of size of parental array) with neutral elements. - assigns the necessary values to the upper "vertexes" of the tree. */ void make_monoid_and_fill_rest(TYPE *NewTree, const size_t &n, TYPE f(TYPE, TYPE), const TYPE &neutr) { g = f; neutral = neutr; for(size_t i = base_capacity+n; i < 2*base_capacity; ++i) { NewTree[i] = neutral; } for(size_t i = base_capacity-1; i > 0; --i) { NewTree[i] = g(NewTree[2*i], NewTree[2*i+1]); } SegmentTree = NewTree; } /* fix_capacity: Rounds base_capacity of the base array to closest upper power of 2. */ void fix_capacity(const size_t &base_array_size) { for (base_capacity = 1; base_capacity < base_array_size; base_capacity <<= 1); } public: /* ########################## #### PUBLIC FUNCTIONS #### ########################## Definitions: n - amount of elements in the parental array; lb - binary logarithm. Lowest level of the tree - parental array (segment of elements, which is a base of the tree) and (if exists) unused space, filled with neutral elements. construct: Constructs the tree by copying elements of [begin; end) memory block into the segment tree, and by building tree's structure using the given binary operation "f" and its neutral element "neutr". Complexity: O(n). */ void construct(const TYPE *begin, const TYPE *end, TYPE f(TYPE, TYPE), const TYPE neutr) { size_t base_size = end - begin; fix_capacity(base_size); TYPE *NewTree = new TYPE[base_capacity*2]; for(size_t i = 0; i < base_size; ++i) { NewTree[base_capacity+i] = begin[i]; } make_monoid_and_fill_rest(NewTree, base_size, f, neutr); } /* read_and_construct: Constructs the tree by filling with it with elements, created by "preprocessing_function", and by building tree's structure using the given binary operation "f" and its neutral element "neutr". This method is useful for creating tree by giving preprocessing_function an ability to read the data from the incoming stream, if the separate memorisation of the base array is not needed. Complexity: O(n). */ void read_and_construct(const size_t amount_of_elements, TYPE preprocessing_function(), TYPE f(TYPE, TYPE), const TYPE neutr) { fix_capacity(amount_of_elements); TYPE *NewTree = new TYPE[base_capacity*2]; for(size_t i = 0; i < amount_of_elements; ++i) { NewTree[base_capacity+i] = preprocessing_function(); } make_monoid_and_fill_rest(NewTree, amount_of_elements, f, neutr); } /* assign: Assigns new value to the element of base array with given index. Complexity: O(lb(n)) */ void assign(const size_t index, const TYPE new_value) { SegmentTree[base_capacity+index] = new_value; for (size_t i = (base_capacity+index)/2; i > 0; i /= 2) { SegmentTree[i] = g(SegmentTree[2*i], SegmentTree[2*i+1]); } } /* result_on_segment: Returns value of operation on the given segment of parental array. Complexity: O(lb(n)). */ TYPE result_on_segment (size_t l, size_t r) { return result_on_segment_in(1, 0, base_capacity-1, l, r); } }; int sum(int a, int b) { return a+b; } int main() { return 0; } |
Описание класса
Далее [latex]n[/latex] — размерность базы дерева.
Название объекта | Описание |
---|---|
Параметр | |
TYPE | Тип объектов дерева, над которыми будут проводится вычисления. |
Внутренние объекты | |
SegmentTree | Массив, хранящий в себе дерево отрезков. |
base_capacity | Переменная, хранящая округлённую к ближайшей большей степени двойки размерность базы дерева отрезков. |
g | Указатель на функцию, которая представляет из себя ассоциативную бинарную операцию. Формально определяется как функция/операция. |
neutral | Нейтральный элемент относительно бинарной операции g. |
Методы класса | |
construct |
Аргументы:
Генерирует базу на основе полуинтервала [latex]\left[a; b\right)[/latex], копируя его элементы внутрь дерева, и строит на основе этой базы дерево отрезков. |
read_and_construct | Аргументы:
Генерирует базу на основе элементов, возвращаемых функцией-препроцессором, и строит на их основе дерево отрезков. |
assign | Аргументы:
Заменяет значение элемента с заданным индексом на новое. |
result_on_segment | Аргументы:
Возвращает результат функции на заданном отрезке. |
Инструкция по применению
Прежде всего, код универсального дерева отрезков необходимо скопировать в исходную программу.
Построение:
- Создать тип объектов (структуру данных), который будет использоваться в дереве для вычислений; (в зависимости от задачи. Вполне может быть, что необходимый для решения задачи класс уже создан. Например — int или double.)
- Инициализировать дерево отрезков, передав классу segments_tree в качестве параметра тип объектов, над которыми будут проводиться вычисления, и задав дереву имя. (инициализация класса segments_tree происходит аналогично инициализации класса vector)
- Построить дерево отрезков на основе заданных элементов при помощи метода construct или read_and_construct, передав методу соответствующие параметры (упомянутые в таблице выше);
Далее для вычисления результатов на отрезках и модификаций элементов с заданным индексом использовать методы result_on_segment и assign соответственно.
Пример использования
Примечание: условие и альтернативное решение приведённой ниже задачи находится по этой ссылке.
Решение задачи №4082 на www.e-olymp.com
Так как в задаче необходимо выводить знак или произведения на заданных отрезках (либо нуль), то очевидно, что сами числа не интересуют нас. Тогда каждое из них можно представить в виде пары (zero, plus)[latex]= \left(a, b\right) \in \mathbb{B}^{2}[/latex] (где [latex]\mathbb{B}[/latex] — булево множество), где первый элемент пар [latex]a[/latex] будет характеризовать равенство числа нулю, а [latex]b[/latex] — его положительность. Назовём структуру данных пар такого типа number_sign. Функция make_number_sign будет преобразовывать числа типа short в number_sign. Затем определим для этой структуры функцию умножения prod формулой prod(number_sign a, number_sign b)[latex]=[/latex] (a.zero|b.zero, !(a.plus^b.plus));. В первой части формулы используется дизъюнкция, так как произведение нуля и произвольного числа всегда должно возвращать нуль, а во второй части — эквиваленция, так как результат произведения является отрицательным, если оба аргумента различны по знаку.
Затем, предварительно считав размер базы, конструируем дерево отрезков методом read_and_construct, передавая ему число элементов базы, анонимную функцию-препроцессор, которая считывает элементы базы из входного потока и которая преобразует их в тип данных number_sign, функцию произведения prod и её нейтральный элемент number_sign(), являющийся парой [latex]\left(0, 1\right)[/latex], который по сути представляет из себя число [latex]+1[/latex] (нейтральный элемент умножения).
Остальная часть решения требует только замены старых элементов новыми и вычислений результатов на отрезках, для чего есть готовые методы класса.
Фрагмент кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
#include <iostream> using namespace std; // . . . . . . . . . . . . . . . . . . . . // . . . . segments_tree class . . . . . . // . . . . . . . . . . . . . . . . . . . . struct number_sign { bool zero, plus; number_sign() { zero = 0; plus = 1; } number_sign(bool a, bool b) { zero = a; plus = b; } }; number_sign make_number_sign(const short &X) { return number_sign(X == 0, X > 0); } number_sign prod(number_sign a, number_sign b) { return number_sign(a.zero|b.zero, !(a.plus^b.plus)); } char sign(const number_sign d) { if (d.zero) return '0'; else if (d.plus) return '+'; else return '-'; } int main() { unsigned int i, j, m; char q; short X; segments_tree <number_sign> A; number_sign *B, neutr; while (cin >> i) { scanf("%u", &m); A.read_and_construct(i, [] () {short X; scanf("%hd", &X); return make_number_sign(X);}, prod, number_sign()); scanf("\n"); ++m; while (--m) { scanf("%c%u", &q, &i); if (q == 'P') { scanf("%u", &j); printf("%c", sign(A.result_on_segment(i-1, j-1))); } else { scanf("%hd", &X); A.assign(i-1, make_number_sign(X)); } scanf("\n"); } printf("\n"); } return 0; } |
Задача 2
Дополнить класс «дерево отрезков» из первой задачи таким образом, чтобы для базы дерева были реализованы:
- параметры «вместимость» и «размер»;
- функции добавления нового элемента в базу;
- функции, возвращающие размер базы и вместимость дерева;
- функция изменения размера базы.
Написать таблицу новых функций и параметров.
Код класса
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
#include <iostream> using namespace std; template <class TYPE> class segments_tree { TYPE *SegmentTree; size_t base_capacity = 0; TYPE (*g)(TYPE, TYPE); TYPE neutral; /* TYPE: Type of elements in the tree. SegmentTree: (pointer) Array-based tree of elements. base_capacity: (unsigned integer value) Parental array size, rounded to the closest upper power of 2. g: (pointer) Pointer to the funtion (associative binary operation), applied on the given array of elements. WARNING: The given function must be associative. The monoidal structure of the tree will be ruined otherwise. neutral: (array element) The value of element, which is neutral relatively to "g" function. ################################ #### INNER HELPER FUNCTIONS #### ################################ result_on_segment_in: returns value on segment [l, r]; */ TYPE result_on_segment_in ( size_t v, size_t tl, size_t tr, size_t l, size_t r ) { if (l > r) return neutral; else if (l == tl && r == tr) return SegmentTree[v]; else { size_t tm = (tl + tr) / 2; return g(result_on_segment_in(v*2, tl, tm, l, min(r,tm)), result_on_segment_in(v*2+1, tm+1, tr, max(l,tm+1), r)); } } /* make_monoid_and_fill_rest: - fills the unused space of array (which appears due to rounding of size of parental array) with neutral elements. - assigns the necessary values to the upper "vertexes" of the tree. */ void make_monoid_and_fill_rest(TYPE *NewTree, const size_t &n, TYPE f(TYPE, TYPE), const TYPE &neutr) { g = f; neutral = neutr; for(size_t i = base_capacity+n; i < 2*base_capacity; ++i) { NewTree[i] = neutral; } for(size_t i = base_capacity-1; i > 0; --i) { NewTree[i] = g(NewTree[2*i], NewTree[2*i+1]); } SegmentTree = NewTree; } /* fix_capacity: Rounds base_capacity of the base array to closest upper power of 2. */ void fix_capacity(const size_t &base_array_size) { for (base_capacity = 1; base_capacity < base_array_size; base_capacity <<= 1); } public: /* ########################## #### PUBLIC FUNCTIONS #### ########################## Definitions: n - amount of elements in the parental array; lb - binary logarithm. Lowest level of the tree - parental array (segment of elements, which is a base of the tree) and (if exists) unused space, filled with neutral elements. construct: Constructs the tree by copying elements of [begin; end) memory block into the segment tree, and by building tree's structure using the given binary operation "f" and its neutral element "neutr". Complexity: O(n). */ void construct(const TYPE *begin, const TYPE *end, TYPE f(TYPE, TYPE), const TYPE neutr) { size_t base_size = end - begin; fix_capacity(base_size); TYPE *NewTree = new TYPE[base_capacity*2]; for(size_t i = 0; i < base_size; ++i) { NewTree[base_capacity+i] = begin[i]; } make_monoid_and_fill_rest(NewTree, base_size, f, neutr); } /* read_and_construct: Constructs the tree by filling with it with elements, created by "preprocessing_function", and by building tree's structure using the given binary operation "f" and its neutral element "neutr". This method is useful for creating tree by giving preprocessing_function an ability to read the data from the incoming stream, if the separate memorisation of the base array is not needed. Complexity: O(n). */ void read_and_construct(const size_t amount_of_elements, TYPE preprocessing_function(), TYPE f(TYPE, TYPE), const TYPE neutr) { fix_capacity(amount_of_elements); TYPE *NewTree = new TYPE[base_capacity*2]; for(size_t i = 0; i < amount_of_elements; ++i) { NewTree[base_capacity+i] = preprocessing_function(); } make_monoid_and_fill_rest(NewTree, amount_of_elements, f, neutr); } /* assign: Assigns new value to the element of base array with given index. Complexity: O(lb(n)) */ void assign(const size_t index, const TYPE new_value) { SegmentTree[base_capacity+index] = new_value; for (size_t i = (base_capacity+index)/2; i > 0; i /= 2) { SegmentTree[i] = g(SegmentTree[2*i], SegmentTree[2*i+1]); } } /* result_on_segment: Returns value of operation on the given segment of parental array. Complexity: O(lb(n)). */ TYPE result_on_segment (size_t l, size_t r) { return result_on_segment_in(1, 0, base_capacity-1, l, r); } }; int main() { return 0; } |
Описание дополнительных объектов класса
Название объекта | Описание |
---|---|
Новый внутренний объект | |
base_size | Переменная, хранящая размерность базы дерева отрезков. |
Новые методы класса | |
begin |
Аргументы: отсутствуют. |
end | Аргументы: отсутствуют. Возвращает адрес конца базы. Вычислительная сложность: константа. |
push_back | Аргумент: значение нового элемента базы. Добавляет новый элемент в конец базы. Вычислительная сложность: если база заполнена, то [latex]O\left(n\right)[/latex], иначе — [latex]O\left(\log_{2}{n}\right)[/latex]. |
pop_back |
Аргументы: отсутствуют. |
insert |
Аргументы:
Добавляет на заданную позицию базы новый элемент с заданным значением. |
erase |
Аргумент: индекс удаляемого элемента. |
size |
Аргументы: отсутствуют. |
capacity |
Аргументы: отсутствуют. |
resize |
Аргумент: новый размер базы. |
Ссылки
- Код универсального дерева отрезков;
- Код универсального дерева отрезков (дополнение функциями вектора);
- Код универсального дерева отрезков (множественная модификация);
- Статья о дереве отрезков на e-maxx.ru, за которую выражаю unsigned long long int (то есть очень большую 🙂 ) благодарность её автору.