Push Free Segment Tree
Push Free Segment Tree
Nikita Gaevoy
Figure 1: Tiffany
1 Prerequisites
First of all, who this article is aimed at. This article assumes you already know what a
segment tree is. Have you never heard about the segment tree, the Fenwick tree (aka binary
indexed tree, BIT) or the RMQ problem, you should better read about it somewhere else and
then come back here right after to learn a bunch of cool stuff. The model target audience
is the people able to solve this problem at least in theory. But even if you can’t this article
could still be helpful. However, I think this article contains some ideas that were never
published before, thus making it interesting even for people who are closely familiar with
different variants of the segment tree. And if you are somewhere in the middle, like you know
about segment trees, but find stuff like historical sums too complicated, this article is still
for you, because it will make those super-easy to understand. Also, there will be a link to
the code at the end, which you may take and use.
1
On a side note, if you are a huge fan of Fenwick tree or using some trees for solving RMQ,
the vast majority of this article will still be applicable to your favorite data structure; I just
decided to focus on the data structure I find simplest and, probably, the most feature-rich.
So, let’s start!
1. Associativity of addition: 𝑥 + (𝑦 + 𝑧) = (𝑥 + 𝑦) + 𝑧.
2. Associativity of multiplication: 𝑎 · (𝑏 · 𝑐) = (𝑎 · 𝑏) · 𝑐.
4. Having zero: 0 · 𝑎 = 0.
5. Having one: 𝑥 · 1 = 𝑥.
This is actually almost a semiring, we only miss the requirement of the addition to be
commutative. Unfortunately, there is no algebraic name for such a structure, so I will stick
to almost-a-semiring.
An important note: In fact, multipliers and summands don’t have to belong to the same
set or, from the programming point of view, type; moreover, implementation-wise it makes
much more sense to have two different template types and, in fact, I use two different sets of
2
symbols for summands (𝑥, 𝑦, 𝑧, . . .) and multipliers (𝑎, 𝑏, 𝑐, . . .), but for the sake of simplicity,
I will keep pretending this opportunity does not exist.
At this moment it may look like we did a lot of boring algebraic work to gain nothing.
At least, it seems like a segment tree can do all of that and even much more beyond. But,
in fact, it does not and almost all operations a segment tree can perform fit in this exact
framework.
Examples:
• max and addition, tropical semiring. Again, fulfills all the requirements, zero and one
are infinity and zero, respectively.
• Range-sum and range-addition. At the first glance does not seem to fulfil the distribu-
tivity, while definitely being viable for a segment tree. However, it would work, if we
add the size of a subsegment to the value in the node and use the following rules:
⟨𝑥, 𝑦⟩ + ⟨𝑧, 𝑡⟩ = ⟨𝑥 + 𝑧, 𝑦 + 𝑡⟩
⟨𝑥, 𝑦⟩ · 𝑎 = ⟨𝑥 + 𝑎 · 𝑦, 𝑦⟩.
Then all our leaves are of the form ⟨𝑥, 1⟩. Obviously, we don’t have to store the size
explicitly, but it is important to notice that it still participates in resulting formulas.
• Matrices, linear functions, polynomials, nimbers. Nothing tricky with them, but not
excessive to mention as well.
• Historical sums! If you are unfamiliar with this idea, it is a trick over segment trees
that allows you to answer the following types of queries on two “arrays” 𝐴 and 𝐵:
And actually, those are much easier at this point, because for them, you are just adding
piecewise linear functions representing how values on the array 𝐵 changes over time
with the last segment (that represents the future) having the slope of 𝐴. The only
important thing is to notice that you can store only the last segment of each function,
because the time always moves forward. And that is it! Look how much simpler than
tutorials on historical sums that are full of formulas over multiple segment trees!
• Almost any other variant of the segment tree named after some red Chinese partici-
pant. The only variant that stand out a bit is the segment tree beats, because unlike
other variants of the segment tree its running time is amortized. Maybe a name like
“amortized segment tree” would have been more sound than the current one. At least,
it would reflect the main difference and be less animeish effectively attracting more
problemsetters to the topic.
3
3 Multiple dimensions
We have developed a language in the previous section, and now we can move on to discuss
new things. You may say that the multidimensional segment tree is not anything new: you
just store a segment tree inside a segment tree, do the same operations as in one-dimensional
case, and you are fine. And I almost agree, but what you do when you want to add on a
rectangle and then also compute a sum in a rectangle? Well, you just store a segment tree
inside a segment tree and then do the same operations as in one-dimensional case, you just
need to check that push works. . . Oh, wait! push! You can’t push the whole segment tree!
And it indeed looks like a problem. Moreover, there were a lot of discussions for similar
multidimensional cases. First there was an article by Laakeri who pointed out that in the
most general case of arbitrary dimension such data structure cannot exist unless ETH falls.
Then, there was the problem D in Yuhao Du Contest 7 (all links could be found here) claiming
that an offline variant of two-dimensional case is possible. And now, I am going to present
another trick to solve some other variants of this problem. What is even more important,
this trick would eventually help us to cut in half both our time and memory usage in the
one-dimensional case under some not so restricting conditions.
So, the trick. Let’s focus exactly on the problem of an addition on a rectangle and
computing the sum on a rectangle. Yes, I know that we can do prefix-sums and probably
be fine without range modifications, but that is not the point. Our problem is that we can’t
push. And we can’t make our inner segment trees be pushable, thus, we have to invent a
segment tree without pushes. And it is possible!
Actually, I was definitely not the first one to come up with a similar data structure, even
though, I never saw any information on it posted publicly. But, at least, Alex_2oo8 told me
that he sometimes implements a similar data structure in some simple cases when I told him
about it. Here I will describe the data structure in its most general form and with a couple
of important optimizations that, to my knowledge, were never known before.
Let’s firstly focus on a one-dimensional case where we want to make range-additions and
compute range-sums. Similarly to the general case, we can store two values in a node: the
sum on a subrange corresponding to that node and the value of our “laziness” that is what
we have to add (in a sense of multiplication) to all our descendants. The difference is that
we would never have to push our second value, “laziness”. Instead of that, we will apply it
each time we exit the node.
Here roughly how we computed range-sum before:
15 update(v);
16
4
17 return ans;
18 }
And in order to solve the problem with rectangular addition and rectangular sum, the
only thing we need to change is to additionally perform queries to the inner tree in computing
the sum:
The addition part does not change too much comparing to ordinary two-dimensional data
structures, but updating is a little tricky:
5
6 if (r <= cl || cr <= l)
7 return;
8 if (l <= cl && cr <= r)
9 {
10 laziness[v].range_add(bot, top, x);
11
20 value[v] += add;
21
22 return add;
23 }
But what we actually need from the segment tree for this trick to work? Commutative
property of multiplication only: 𝑎 · 𝑏 = 𝑏 · 𝑎. However, in multidimensional case this property
turns out to be quite restrictive (remember that in that case our elements are stripes, not the
values, and see how it changed the updating part in rectangle_add) making it hard to come
up with almost any other natural example except for rectangular addition and rectangular
sum.
But in one-dimensional case the push-free segment tree (this is how I named it about 3.5
years ago when it came up during some training contest) allows us to write the code without
pushes giving the possibility to mark sum queries const and improving the const-safety of
your code. But it does not stop here!
6
0..16
0..8 8..16
0..4 4..8 8..12 12..16
0..2 2..4 4..6 6..8 8..10 10..12 12..14 14..16
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
two (when exist) are placed right outside the range we are maintaining when we are running
our loop for multiplication. In order not to accidentally access outside the memory (and not
to do range-checks) it is wise to have the underlying vector of size at least 2𝑛+2 while having
the root at the index 1 (which is actually the best index for the root in all implementations
of the segment tree). The second thing we have to remember is that we always have to go
the full path to the root, because there could be some nontrivial multipliers or nodes you
still have to update. And the last optimization is to change the convention of storing our
values a little. If we store not the pair of the sum of the children and the lazied operation to
the subtree, but rather the sum with this operation already applied and the operation itself,
we would save one additional multiplication per update, which gives us a couple of tens of
percents of speedup, depending on the operations themselves. Always nice to have.
And overall, we built a data structure that for any commutative almost-a-semiring is
able to do whatever the regular recursive segment tree with lazy propagation can do, but in
time and space of the segment tree upwards, which is practically roughly two times better
in both parameters! Also, if you think that commutativity is a very restrictive property,
remember that sometimes you may slightly change your operations to fulfill it. For example,
very non-commutative assignment can become commutative remax, if you add the time of
assignment, which makes range-max and range-assignment a viable pair of operations for a
push-free segment tree.
Here is the implementation I made with help of Burunduk1 for our team reference docu-
ment for the World Finals that implements all the tricks above. The repository (here is the
link to its root) contains a lot of cool stuff, so if you find something else in it useful, feel free
to use it as well.
5 Off Topic
Having such an opportunity, I also want to promote our (made jointly with tranquility and
Kaban-5) new contest that was named SPb SU LOUD Enough Contest 2 in Petrozavodsk
Winter Camp and hopefully will eventually be one of OpenCup GPs as well (probably,
somewhere in Spring). We think that its problems are full of great ideas and we are willing
to share them with the community to commemorate our retirement from the ICPC career.
And if you are reading it somewhere in the future, it is still probably a great contest to train
on!
Acknowledgements
Many thanks to Boris, Liana, Vanya and Vlad who read this text prior to publication. Also,
special thanks to Katya for the picture of Tiffany that should attract two times more readers
to this text than the sacral knowledge it contains.