Привет, хабр!
Это моя вторая статья из серии «то что я не нашел в интернетах». Кому интересно — добро пожаловать под кат.
Начнем с постановки задачи:
Значение суммы мы будем хранить в массиве value, а значение которое нужно присвоить элементам данного поддерева будем хранить в массиве delta. Первичное заполнение массивов ничем не отличается от стандартного дерева отрезков:
Теперь нужно научиться обновлять значение на отрезке. Для этого мы будем использовать «отложенные модификации», то есть будем помечать новым значеним только корни соответсвующих поддеревьев:
Теперь остается только научиться пересчитывать сумму на отрезке, если это необходимо:
На этом, вроде, все. Снова надеюсь, что кому-то пригодится.
p.s. Часть код взята и в некоторых местах переделанна из библиотеки Chelper'a.
Это моя вторая статья из серии «то что я не нашел в интернетах». Кому интересно — добро пожаловать под кат.
Начнем с постановки задачи:
- Дан массив из N элементов, где 1 ? N ? 100 000,
- и K запросов (1 <= K <= 100 000) вида:
- Присвоить элементам с L по R значение V (0 ? V ? 100 000 000)
- Найти сумму элементов отрезка [L; R]
Задача
Значение суммы мы будем хранить в массиве value, а значение которое нужно присвоить элементам данного поддерева будем хранить в массиве delta. Первичное заполнение массивов ничем не отличается от стандартного дерева отрезков:
long[] value;
long[] delta;
long default_delta = -1; // должно быть невозможным значением
void init(long[] a){
int nodes = a.length << 2;
tree = new long[nodes];
delta = new long[nodes];
build(a, 1, 0, a.length-1);
}
void build(long[] a, int v,int tl,int tr){
if(tl == tr)
tree[v] = a[tl];
else{
int mid = (tl+tr)>>1;
build(a, 2*v, tl, mid);
build(a, 2*v+1, mid+1, tr);
tree[v] = tree[v*2]+tree[v*2+1];
delta[v] = default_delta;
}
}
Теперь нужно научиться обновлять значение на отрезке. Для этого мы будем использовать «отложенные модификации», то есть будем помечать новым значеним только корни соответсвующих поддеревьев:
void update(int root, int left, int right, int from, int to, long delta) {
if (left > to || right < from)
return;
if (left >= from && right <= to) {
updateFull(root, left, right, from, to, delta);
return;
}
int middle = (left + right) >> 1;
updatePreProcess(root, left, right, from, to, delta, middle);
update(2 * root + 1, left, middle, from, to, delta);
update(2 * root + 2, middle + 1, right, from, to, delta);
updatePostProcess(root, left, right, from, to, delta, middle);
}
void updateFull(int root, int left, int right, int from, int to, long delta) {
value[root] = accumulate(value[root], delta, right - left + 1);
delta[root] = joinDelta(delta[root], delta);
}
void updatePreProcess(int root, int left, int right, int from, int to, long delta, int middle) {
pushDown(root, left, middle, right);
}
void updatePostProcess(int root, int left, int right, int from, int to, long delta, int middle) {
value[root] = joinValue(value[2 * root + 1], value[2 * root + 2]);
}
void pushDown(int root, int left, int middle, int right) {
value[2 * root + 1] = accumulate(value[2 * root + 1], delta[root], middle - left + 1);
value[2 * root + 2] = accumulate(value[2 * root + 2], delta[root], right - middle);
delta[2 * root + 1] = joinDelta(delta[2 * root + 1], delta[root]);
delta[2 * root + 2] = joinDelta(delta[2 * root + 2], delta[root]);
delta[root] = default_delta;
}
long accumulate(long value, long delta, int length) {
if(delta != default_delta)
return delta * length;
return value;
}
long joinDelta(long was, long delta) {
if(delta != default_delta) //
return delta;
return was;
}
long joinValue(long left, long right) {
return left + right;
}
Теперь остается только научиться пересчитывать сумму на отрезке, если это необходимо:
long query(int root, int left, int right, int from, int to) {
if (left > to || right < from)
return 0;
if (left >= from && right <= to)
return queryFull(root, left, right, from, to);
int middle = (left + right) >> 1;
queryPreProcess(root, left, right, from, to, middle);
long leftResult = query(2 * root + 1, left, middle, from, to);
long rightResult = query(2 * root + 2, middle + 1, right, from, to);
return queryPostProcess(root, left, right, from, to, middle, leftResult, rightResult);
}
void queryPreProcess(int root, int left, int right, int from, int to, int middle) {
pushDown(root, left, middle, right);
}
long queryPostProcess(int root, int left, int right, int from, int to, int middle, long leftResult, long rightResult) {
return joinValue(leftResult, rightResult);
}
long queryFull(int root, int left, int right, int from, int to) {
return value[root];
}
На этом, вроде, все. Снова надеюсь, что кому-то пригодится.
p.s. Часть код взята и в некоторых местах переделанна из библиотеки Chelper'a.
Комментарии (5)
Hippskill Автор
04.08.2015 00:29-4Целью этой статьи не является объяснение что такое дерево отрезков и с чем его едят, таких статей уж слишком много и я не хотел их банально копировать, так что описал лишь про то, чего сам найти не смог.
roman_kashitsyn
04.08.2015 08:28то что я не нашел в интернетах
Вот, например, довольно подробная статья: e-maxx.ru/algo/segment_tree
schroeder
Извините, это что и как? А где теория? Где обьяснения, что такое дерево отрезков, как работает, где применяется, зачем вообще придумано? Ну хоть чуть-чуть, что было за что глазу зацепится.
sashagil
Мне кажется, это похоже на дерево Фенвика или binary indexed tree — на сайте спортивного программирования TopCoder есть подробное описание на английском: www.topcoder.com/community/data-science/data-science-tutorials/binary-indexed-trees. Отличие (пардон, если ошибаюсь) в том, что здесь автор сразу реализовывает обновление на отрезке, а не в единственном элементе; по-моему, это не принципиальное отличие.
sashagil
Пардон, поторопился / промахнулся — здесь всё же идёт речь о дереве отрезков (кстати, про дерево Фенвика писали на Хабре: habrahabr.ru/post/112828).