Introduction
Segment trees are commonly used in algorithm contests to maintain range information.
Segment trees support point updates, range updates, and range queries (range sums, range maxima, and range minima) in time.
Basic Structure and Building a Segment Tree
Process
A segment tree recursively divides every interval of length greater than into a left interval and a right interval, turning the entire segment into a tree structure. The information for an interval is obtained by merging the information of its two subintervals. This data structure makes most range operations convenient.
Consider an array of size , . To turn it into a segment tree, let the root of the segment tree have index , and use an array to store the segment tree. stores its value of this node with index in the segment tree; here, each node stores the sum of this interval it represents.
We first show the shape of this segment tree, as in the figure:
The intervals marked in red in the figure are the position intervals of the array managed by each node. For example, this interval managed by is (that is, ), so its value stored in is . The statement means .
It is easy to see that the left child of is and the right child of is . If represents this interval (that is, ), then the left child of represents , and the right child of represents .
In the implementation, we build the tree recursively. Let the current root be node . If this interval managed by the root already has length , initialize this node directly from the corresponding value in the array. Otherwise, split this interval at its midpoint into two subintervals, recursively build the left and right children, and finally merge the information from the two children.
Implementation
Here is a code implementation; the comments may help explain it:
C++
void build(int s, int t, int p) {
// Build a segment tree for [s,t]; p is the index of the current root
if (s == t) {
d[p] = a[s];
return;
}
int m = s + ((t - s) >> 1);
// The shift operator has lower precedence than addition and subtraction, so add parentheses
// Writing (s + t) >> 1 may overflow the int range
build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
// Recursively build the tree for the left and right intervals
d[p] = d[p * 2] + d[(p * 2) + 1];
}
Python
def build(s, t, p):
# Build a segment tree for [s,t]; p is the index of the current root
if s == t:
d[p] = a[s]
return
m = s + ((t - s) >> 1)
# The shift operator has lower precedence than addition and subtraction, so add parentheses
# Writing (s + t) >> 1 may overflow the int range
build(s, m, p * 2)
build(m + 1, t, p * 2 + 1)
# Recursively build the tree for the left and right intervals
d[p] = d[p * 2] + d[(p * 2) + 1]
Regarding the space used by a segment tree: with heap-style storage ( is the left child of , and is the right child of ), if there are leaf nodes, the maximum range of the array d is .
Analysis: the depth of a segment tree is clearly . Under heap-style storage, the number of leaf nodes (including unused leaves) is , and because the tree is a complete binary tree, its total number of nodes is . If you do not want to calculate this, you can simply set the array length to , because the maximum of is attained when . In that case, this node count is .
Heap-style storage has unused leaf nodes, so a memory pool can be used to manage segment-tree nodes: obtain a node from the pool whenever a new node is needed. Working upward from the bottom, every two nodes at the lowest level must merge into one node at the next level. Thus, by an argument similar to the one for a Huffman tree, a segment tree with leaf nodes has nodes in total. Its space efficiency is better than heap-style storage and is also optimal.
Such a segment tree can be maintained from the bottom up; see Statistical Power - Zhang Kunwei.
Range Queries on a Segment Tree
Process
A range query may ask for the sum of an interval (that is, ), or for its maximum or minimum.
Using the original figure as an example, to query the sum of , simply obtain its value of , namely .
If this query interval is , its value cannot be obtained directly. However, can be split into and , and the answer for this interval can be obtained by merging the answers for these two intervals.
In general, if this query interval is , it can be split into at most maximal intervals, whose answers can then be merged to obtain the answer for .
Implementation
Here is a code implementation; the comments may help explain it:
C++
int getsum(int l, int r, int s, int t, int p) {
// [l, r] is this query interval, [s, t] is this interval contained by the current node, and p is the current node's index
if (l <= s && t <= r)
return d[p]; // Return the sum of the current interval directly when it is contained in this query interval
int m = s + ((t - s) >> 1), sum = 0;
if (l <= m) sum += getsum(l, r, s, m, p * 2);
// If this interval [s, m] represented by the left child intersects this query interval, recursively query the left child
if (r > m) sum += getsum(l, r, m + 1, t, p * 2 + 1);
// If this interval [m + 1, t] represented by the right child intersects this query interval, recursively query the right child
return sum;
}
Python
def getsum(l, r, s, t, p):
# [l, r] is this query interval, [s, t] is this interval contained by the current node, and p is the current node's index
if l <= s and t <= r:
return d[p] # Return the sum of the current interval directly when it is contained in this query interval
m = s + ((t - s) >> 1)
sum = 0
if l <= m:
sum = sum + getsum(l, r, s, m, p * 2)
# If this interval [s, m] represented by the left child intersects this query interval, recursively query the left child
if r > m:
sum = sum + getsum(l, r, m + 1, t, p * 2 + 1)
# If this interval [m + 1, t] represented by the right child intersects this query interval, recursively query the right child
return sum
Range Updates and Lazy Propagation in a Segment Tree
Process
If we need to modify an interval by visiting and modifying every node contained in , the time complexity is unacceptable. We therefore introduce lazy tags.
In simple terms, a lazy tag delays changes to node information, reducing potentially unnecessary operations. During each update, we attach a tag to indicate that this interval represented by this node was changed by an operation, but do not update the information of its children. The actual modification is performed only when a tagged node is visited later.
Again using the original figure, suppose we perform several operations that add a value to every number in an interval. We now add a to every node to represent its value of the tag carried by that node.
The initial state is as follows (this intervals managed by individual nodes are omitted to save space):
Now, for , add to every number. Based on the range-query discussion above, we quickly find the two maximal intervals and (corresponding to nodes and in the segment tree, respectively).
We update these two nodes directly and attach tags to them:
We can see that the information in node has been updated (because its interval contains two numbers, the amount added to is ), but its two children have not yet been updated and still retain their old information. There is no need to worry: although the update has not yet reached them, when we query the information of these two children, we use the tag to update their information, so this query result remains correct.
Next, query the sum of the numbers in this interval .
Following the recursion, we find this interval . It is not our target interval, and it still has a tag. This is when the tag must be pushed down. Update the information of its two child intervals and clear the tag on this interval.
The values of nodes and are now up to date, and this query result is also correct.
Implementation
We next give reference implementations of range updates and queries in the presence of tags.
Range update (add a value to an interval):
C++
// [l, r] is the update interval, c is the change applied to each element, [s, t] is this interval contained by the current node, and p
// is the current node's index
void update(int l, int r, int c, int s, int t, int p) {
// When the current interval is contained in the update interval, modify the current node's value directly, attach a tag, and finish the update
if (l <= s && t <= r) {
d[p] += (t - s + 1) * c, b[p] += c;
return;
}
int m = s + ((t - s) >> 1);
if (b[p] && s != t) {
// If the current node has a nonempty lazy tag, update its values and lazy tags of its two children
d[p * 2] += b[p] * (m - s + 1), d[p * 2 + 1] += b[p] * (t - m);
b[p * 2] += b[p], b[p * 2 + 1] += b[p]; // Push the tag down to the children
b[p] = 0; // Clear the current node's tag
}
if (l <= m) update(l, r, c, s, m, p * 2);
if (r > m) update(l, r, c, m + 1, t, p * 2 + 1);
d[p] = d[p * 2] + d[p * 2 + 1];
}
Python
def update(l, r, c, s, t, p):
# [l, r] is the update interval, c is the change applied to each element, [s, t] is this interval contained by the current node, and p
# is the current node's index
if l <= s and t <= r:
d[p] = d[p] + (t - s + 1) * c
b[p] = b[p] + c
return
# When the current interval is contained in the update interval, modify the current node's value directly, attach a tag, and finish the update
m = s + ((t - s) >> 1)
if b[p] and s != t:
# If the current node has a nonempty lazy tag, update its values and lazy tags of its two children
d[p * 2] = d[p * 2] + b[p] * (m - s + 1)
d[p * 2 + 1] = d[p * 2 + 1] + b[p] * (t - m)
# Push the tag down to the children
b[p * 2] = b[p * 2] + b[p]
b[p * 2 + 1] = b[p * 2 + 1] + b[p]
# Clear the current node's tag
b[p] = 0
if l <= m:
update(l, r, c, s, m, p * 2)
if r > m:
update(l, r, c, m + 1, t, p * 2 + 1)
d[p] = d[p * 2] + d[p * 2 + 1]
Range query (range sum):
C++
int getsum(int l, int r, int s, int t, int p) {
// [l, r] is this query interval, [s, t] is this interval contained by the current node, and p is the current node's index
if (l <= s && t <= r) return d[p];
// Return the sum of the current interval directly when it is contained in this query interval
int m = s + ((t - s) >> 1);
if (b[p]) {
// If the current node has a nonempty lazy tag, update its values and lazy tags of its two children
d[p * 2] += b[p] * (m - s + 1), d[p * 2 + 1] += b[p] * (t - m);
b[p * 2] += b[p], b[p * 2 + 1] += b[p]; // Push the tag down to the children
b[p] = 0; // Clear the current node's tag
}
int sum = 0;
if (l <= m) sum = getsum(l, r, s, m, p * 2);
if (r > m) sum += getsum(l, r, m + 1, t, p * 2 + 1);
return sum;
}
Python
def getsum(l, r, s, t, p):
# [l, r] is this query interval, [s, t] is this interval contained by the current node, and p is the current node's index
if l <= s and t <= r:
return d[p]
# Return the sum of the current interval directly when it is contained in this query interval
m = s + ((t - s) >> 1)
if b[p]:
# If the current node has a nonempty lazy tag, update its values and lazy tags of its two children
d[p * 2] = d[p * 2] + b[p] * (m - s + 1)
d[p * 2 + 1] = d[p * 2 + 1] + b[p] * (t - m)
# Push the tag down to the children
b[p * 2] = b[p * 2] + b[p]
b[p * 2 + 1] = b[p * 2 + 1] + b[p]
# Clear the current node's tag
b[p] = 0
sum = 0
if l <= m:
sum = getsum(l, r, s, m, p * 2)
if r > m:
sum = sum + getsum(l, r, m + 1, t, p * 2 + 1)
return sum
If you want a range update that assigns one value rather than adding a value, use the following code:
C++
void update(int l, int r, int c, int s, int t, int p) {
if (l <= s && t <= r) {
d[p] = (t - s + 1) * c, b[p] = c, v[p] = 1;
return;
}
int m = s + ((t - s) >> 1);
// An auxiliary array stores whether an assignment has been made
if (v[p]) {
d[p * 2] = b[p] * (m - s + 1), d[p * 2 + 1] = b[p] * (t - m);
b[p * 2] = b[p * 2 + 1] = b[p];
v[p * 2] = v[p * 2 + 1] = 1;
v[p] = 0;
}
if (l <= m) update(l, r, c, s, m, p * 2);
if (r > m) update(l, r, c, m + 1, t, p * 2 + 1);
d[p] = d[p * 2] + d[p * 2 + 1];
}
int getsum(int l, int r, int s, int t, int p) {
if (l <= s && t <= r) return d[p];
int m = s + ((t - s) >> 1);
if (v[p]) {
d[p * 2] = b[p] * (m - s + 1), d[p * 2 + 1] = b[p] * (t - m);
b[p * 2] = b[p * 2 + 1] = b[p];
v[p * 2] = v[p * 2 + 1] = 1;
v[p] = 0;
}
int sum = 0;
if (l <= m) sum = getsum(l, r, s, m, p * 2);
if (r > m) sum += getsum(l, r, m + 1, t, p * 2 + 1);
return sum;
}
Python
def update(l, r, c, s, t, p):
if l <= s and t <= r:
d[p] = (t - s + 1) * c
b[p] = c
v[p] = 1
return
m = s + ((t - s) >> 1)
if v[p]:
d[p * 2] = b[p] * (m - s + 1)
d[p * 2 + 1] = b[p] * (t - m)
b[p * 2] = b[p * 2 + 1] = b[p]
v[p * 2] = v[p * 2 + 1] = 1
v[p] = 0
if l <= m:
update(l, r, c, s, m, p * 2)
if r > m:
update(l, r, c, m + 1, t, p * 2 + 1)
d[p] = d[p * 2] + d[p * 2 + 1]
def getsum(l, r, s, t, p):
if l <= s and t <= r:
return d[p]
m = s + ((t - s) >> 1)
if v[p]:
d[p * 2] = b[p] * (m - s + 1)
d[p * 2 + 1] = b[p] * (t - m)
b[p * 2] = b[p * 2 + 1] = b[p]
v[p * 2] = v[p * 2 + 1] = 1
v[p] = 0
sum = 0
if l <= m:
sum = getsum(l, r, s, m, p * 2)
if r > m:
sum = sum + getsum(l, r, m + 1, t, p * 2 + 1)
return sum
Dynamic Segment Trees
As noted above, heap-style storage requires an array of size for a segment tree. To save space, we can avoid building the whole tree at once and initially create only a root node representing the entire interval. A child node representing a subinterval is created only when that subinterval needs to be accessed. We no longer use and as the children of node ; instead, we use and to store the child indices. In short, the central idea of a dynamic segment tree is: a node is created only when it is needed.
The time complexity of a single operation remains . Since each operation may create and visit an entirely new sequence of nodes, the number of nodes after point operations is . At most nodes are needed, with no waste.
Point update:
// root is the root of the entire segment tree; cnt is the current number of nodes
int n, cnt, root;
int sum[n * 2], ls[n * 2], rs[n * 2];
// Usage: update(root, 1, n, x, f); x is the index of this node to update
void update(int& p, int s, int t, int x, int f) { // Pass by reference
if (!p) p = ++cnt; // Create a new node when this node is empty
if (s == t) {
sum[p] += f;
return;
}
int m = s + ((t - s) >> 1);
if (x <= m)
update(ls[p], s, m, x, f);
else
update(rs[p], m + 1, t, x, f);
sum[p] = sum[ls[p]] + sum[rs[p]]; // pushup
}
Range query:
// Usage: query(root, 1, n, l, r);
int query(int p, int s, int t, int l, int r) {
if (!p) return 0; // Return 0 when this node is empty
if (s >= l && t <= r) return sum[p];
int m = s + ((t - s) >> 1), ans = 0;
if (l <= m) ans += query(ls[p], s, m, l, r);
if (r > m) ans += query(rs[p], m + 1, t, l, r);
return ans;
}
Range updates are handled similarly, but when pushing down a tag, if a child is missing, create it directly. Alternatively, use the technique of making tags persistent.
Some Optimizations
Here is a summary of several segment-tree optimizations:
-
At a leaf node, there is no need to push down a lazy tag, so lazy tags do not need to be propagated to leaf nodes.
-
Lazy propagation can be written as a dedicated function
pushdown, and updating the current node from its children can likewise be written as a dedicated functionmaintain(or symmetricallypushup), reducing the difficulty of writing the code. -
Tag persistence: if you are sure that a lazy tag will not overflow during this process (that is, exceed the maximum value representable by its data type), the tag can be made persistent. Persistent tags avoid pushing lazy tags down; when answering a query, simply add the tag's effect to the answer, reducing the program constant. The exact handling depends on the problem's properties and should be implemented accordingly. This is also a technique used in segment-tree-over-segment-tree and persistent data structures.
C++ Templates
SegTreeLazyRangeAdd: segment tree template for range addition and sum queries
#include <bits/stdc++.h>
using namespace std;
template <typename T>
class SegTreeLazyRangeAdd {
vector<T> tree, lazy;
vector<T> *arr;
int n, root, n4, end;
void maintain(int cl, int cr, int p) {
int cm = cl + (cr - cl) / 2;
if (cl != cr && lazy[p]) {
lazy[p * 2] += lazy[p];
lazy[p * 2 + 1] += lazy[p];
tree[p * 2] += lazy[p] * (cm - cl + 1);
tree[p * 2 + 1] += lazy[p] * (cr - cm);
lazy[p] = 0;
}
}
T range_sum(int l, int r, int cl, int cr, int p) {
if (l <= cl && cr <= r) return tree[p];
int m = cl + (cr - cl) / 2;
T sum = 0;
maintain(cl, cr, p);
if (l <= m) sum += range_sum(l, r, cl, m, p * 2);
if (r > m) sum += range_sum(l, r, m + 1, cr, p * 2 + 1);
return sum;
}
void range_add(int l, int r, T val, int cl, int cr, int p) {
if (l <= cl && cr <= r) {
lazy[p] += val;
tree[p] += (cr - cl + 1) * val;
return;
}
int m = cl + (cr - cl) / 2;
maintain(cl, cr, p);
if (l <= m) range_add(l, r, val, cl, m, p * 2);
if (r > m) range_add(l, r, val, m + 1, cr, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
void build(int s, int t, int p) {
if (s == t) {
tree[p] = (*arr)[s];
return;
}
int m = s + (t - s) / 2;
build(s, m, p * 2);
build(m + 1, t, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
public:
explicit SegTreeLazyRangeAdd<T>(vector<T> v) {
n = v.size();
n4 = n * 4;
tree = vector<T>(n4, 0);
lazy = vector<T>(n4, 0);
arr = &v;
end = n - 1;
root = 1;
build(0, end, 1);
arr = nullptr;
}
void show(int p, int depth = 0) {
if (p > n4 || tree[p] == 0) return;
show(p * 2, depth + 1);
for (int i = 0; i < depth; ++i) putchar('\t');
printf("%d:%d\n", tree[p], lazy[p]);
show(p * 2 + 1, depth + 1);
}
T range_sum(int l, int r) { return range_sum(l, r, 0, end, root); }
void range_add(int l, int r, T val) { range_add(l, r, val, 0, end, root); }
};
SegTreeLazyRangeSet: segment tree template for range assignment and sum queries
#include <bits/stdc++.h>
using namespace std;
template <typename T>
class SegTreeLazyRangeSet {
vector<T> tree, lazy;
vector<T> *arr;
vector<bool> ifLazy;
int n, root, n4, end;
void maintain(int cl, int cr, int p) {
int cm = cl + (cr - cl) / 2;
if (cl != cr && ifLazy[p]) {
lazy[p * 2] = lazy[p], ifLazy[p * 2] = 1;
lazy[p * 2 + 1] = lazy[p], ifLazy[p * 2 + 1] = 1;
tree[p * 2] = lazy[p] * (cm - cl + 1);
tree[p * 2 + 1] = lazy[p] * (cr - cm);
lazy[p] = 0;
ifLazy[p] = 0;
}
}
T range_sum(int l, int r, int cl, int cr, int p) {
if (l <= cl && cr <= r) return tree[p];
int m = cl + (cr - cl) / 2;
T sum = 0;
maintain(cl, cr, p);
if (l <= m) sum += range_sum(l, r, cl, m, p * 2);
if (r > m) sum += range_sum(l, r, m + 1, cr, p * 2 + 1);
return sum;
}
void range_set(int l, int r, T val, int cl, int cr, int p) {
if (l <= cl && cr <= r) {
lazy[p] = val;
ifLazy[p] = 1;
tree[p] = (cr - cl + 1) * val;
return;
}
int m = cl + (cr - cl) / 2;
maintain(cl, cr, p);
if (l <= m) range_set(l, r, val, cl, m, p * 2);
if (r > m) range_set(l, r, val, m + 1, cr, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
void build(int s, int t, int p) {
if (s == t) {
tree[p] = (*arr)[s];
return;
}
int m = s + (t - s) / 2;
build(s, m, p * 2);
build(m + 1, t, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1];
}
public:
explicit SegTreeLazyRangeSet<T>(vector<T> v) {
n = v.size();
n4 = n * 4;
tree = vector<T>(n4, 0);
lazy = vector<T>(n4, 0);
ifLazy = vector<bool>(n4, 0);
arr = &v;
end = n - 1;
root = 1;
build(0, end, 1);
arr = nullptr;
}
void show(int p, int depth = 0) {
if (p > n4 || tree[p] == 0) return;
show(p * 2, depth + 1);
for (int i = 0; i < depth; ++i) putchar('\t');
printf("%d:%d\n", tree[p], lazy[p]);
show(p * 2 + 1, depth + 1);
}
T range_sum(int l, int r) { return range_sum(l, r, 0, end, root); }
void range_set(int l, int r, T val) { range_set(l, r, val, 0, end, root); }
};
Examples
luogu P3372 [Template] Segment Tree 1
Given a sequence, perform the following two operations:
-
Add to every number in an interval.
-
Find the sum of all numbers in an interval.
Reference implementation
#include <iostream>
using LL = long long;
LL n, a[100005], d[270000], b[270000];
void build(LL l, LL r, LL p) { // l: left endpoint of this interval; r: right endpoint; p: node index
if (l == r) {
d[p] = a[l]; // Assign a value to this node
return;
}
LL m = l + ((r - l) >> 1);
build(l, m, p << 1), build(m + 1, r, (p << 1) | 1); // Build the two subtrees
d[p] = d[p << 1] + d[(p << 1) | 1];
}
void update(LL l, LL r, LL c, LL s, LL t, LL p) {
if (l <= s && t <= r) {
d[p] += (t - s + 1) * c, b[p] += c; // If this interval is contained, obtain the answer directly
return;
}
LL m = s + ((t - s) >> 1);
if (b[p])
d[p << 1] += b[p] * (m - s + 1), d[(p << 1) | 1] += b[p] * (t - m),
b[p << 1] += b[p], b[(p << 1) | 1] += b[p];
b[p] = 0;
if (l <= m)
update(l, r, c, s, m, p << 1); // This line and the next update the p*2 and p*2+1 nodes
if (r > m) update(l, r, c, m + 1, t, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1]; // Compute the sum of this node's interval
}
LL getsum(LL l, LL r, LL s, LL t, LL p) {
if (l <= s && t <= r) return d[p];
LL m = s + ((t - s) >> 1);
if (b[p])
d[p << 1] += b[p] * (m - s + 1), d[(p << 1) | 1] += b[p] * (t - m),
b[p << 1] += b[p], b[(p << 1) | 1] += b[p];
b[p] = 0;
LL sum = 0;
if (l <= m)
sum =
getsum(l, r, s, m, p << 1); // This line and the next update the answers for p*2 and p*2+1
if (r > m) sum += getsum(l, r, m + 1, t, (p << 1) | 1);
return sum;
}
int main() {
std::ios::sync_with_stdio(false);
LL q, i1, i2, i3, i4;
std::cin >> n >> q;
for (LL i = 1; i <= n; i++) std::cin >> a[i];
build(1, n, 1);
while (q--) {
std::cin >> i1 >> i2 >> i3;
if (i1 == 2)
std::cout << getsum(i2, i3, 1, n, 1) << std::endl; // Call an operation function directly
else
std::cin >> i4, update(i2, i3, i4, 1, n, 1);
}
return 0;
}
luogu P3373 [Template] Segment Tree 2
Given a sequence, perform the following three operations:
-
Multiply every number in an interval by .
-
Add to every number in an interval.
-
Find the sum of all numbers in an interval.
Reference implementation
#include <iostream>
using ll = long long;
int n, m;
ll mod;
ll a[100005], sum[400005], mul[400005], laz[400005];
void up(int i) { sum[i] = (sum[(i << 1)] + sum[(i << 1) | 1]) % mod; }
void pd(int i, int s, int t) {
int l = (i << 1), r = (i << 1) | 1, mid = (s + t) >> 1;
if (mul[i] != 1) { // Propagate the two lazy tags
mul[l] *= mul[i];
mul[l] %= mod;
mul[r] *= mul[i];
mul[r] %= mod;
laz[l] *= mul[i];
laz[l] %= mod;
laz[r] *= mul[i];
laz[r] %= mod;
sum[l] *= mul[i];
sum[l] %= mod;
sum[r] *= mul[i];
sum[r] %= mod;
mul[i] = 1;
}
if (laz[i]) { // Propagate the lazy tag
sum[l] += laz[i] * (mid - s + 1);
sum[l] %= mod;
sum[r] += laz[i] * (t - mid);
sum[r] %= mod;
laz[l] += laz[i];
laz[l] %= mod;
laz[r] += laz[i];
laz[r] %= mod;
laz[i] = 0;
}
return;
}
void build(int s, int t, int i) {
mul[i] = 1;
if (s == t) {
sum[i] = a[s];
return;
}
int mid = s + ((t - s) >> 1);
build(s, mid, i << 1); // Build the tree
build(mid + 1, t, (i << 1) | 1);
up(i);
}
void chen(int l, int r, int s, int t, int i, ll z) {
int mid = s + ((t - s) >> 1);
if (l <= s && t <= r) {
mul[i] *= z;
mul[i] %= mod; // Take the modulus here
laz[i] *= z;
laz[i] %= mod; // Take the modulus here
sum[i] *= z;
sum[i] %= mod; // Take the modulus here
return;
}
pd(i, s, t);
if (mid >= l) chen(l, r, s, mid, (i << 1), z);
if (mid + 1 <= r) chen(l, r, mid + 1, t, (i << 1) | 1, z);
up(i);
}
void add(int l, int r, int s, int t, int i, ll z) {
int mid = s + ((t - s) >> 1);
if (l <= s && t <= r) {
sum[i] += z * (t - s + 1);
sum[i] %= mod; // Take the modulus here
laz[i] += z;
laz[i] %= mod; // Take the modulus here
return;
}
pd(i, s, t);
if (mid >= l) add(l, r, s, mid, (i << 1), z);
if (mid + 1 <= r) add(l, r, mid + 1, t, (i << 1) | 1, z);
up(i);
}
ll getans(int l, int r, int s, int t,
int i) { // Obtain the answer; the lazy-tag code above may help with understanding
int mid = s + ((t - s) >> 1);
ll tot = 0;
if (l <= s && t <= r) return sum[i];
pd(i, s, t);
if (mid >= l) tot += getans(l, r, s, mid, (i << 1));
tot %= mod;
if (mid + 1 <= r) tot += getans(l, r, mid + 1, t, (i << 1) | 1);
return tot % mod;
}
using std::cin;
using std::cout;
int main() { // Read the input
cin.tie(nullptr)->sync_with_stdio(false);
int i, j, x, y, bh;
ll z;
cin >> n >> m >> mod;
for (i = 1; i <= n; i++) cin >> a[i];
build(1, n, 1); // Build the tree
for (i = 1; i <= m; i++) {
cin >> bh;
if (bh == 1) {
cin >> x >> y >> z;
chen(x, y, 1, n, 1, z);
} else if (bh == 2) {
cin >> x >> y >> z;
add(x, y, 1, n, 1, z);
} else if (bh == 3) {
cin >> x >> y;
cout << getans(x, y, 1, n, 1) << '\n';
}
}
return 0;
}
HihoCoder 1078 Segment Tree Range Updates
Suppose kinds of goods are arranged from left to right on a shelf and numbered from to , with the price of the item numbered equal to . Each operation by Hi is one of two types. The first changes prices: Hi gives an interval and a new price , and every item whose number lies in this interval is assigned price . The second is a query: Hi gives an interval , and Ho must calculate the total price of all items whose numbers lie in that interval and report it to Hi.
Reference implementation
#include <iostream>
int n, a[100005], d[270000], b[270000];
void build(int l, int r, int p) { // Build the tree
if (l == r) {
d[p] = a[l];
return;
}
int m = l + ((r - l) >> 1);
build(l, m, p << 1), build(m + 1, r, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
void update(int l, int r, int c, int s, int t,
int p) { // Update; see the previous two examples
if (l <= s && t <= r) {
d[p] = (t - s + 1) * c, b[p] = c;
return;
}
int m = s + ((t - s) >> 1);
if (b[p]) {
d[p << 1] = b[p] * (m - s + 1), d[(p << 1) | 1] = b[p] * (t - m);
b[p << 1] = b[(p << 1) | 1] = b[p];
b[p] = 0;
}
if (l <= m) update(l, r, c, s, m, p << 1);
if (r > m) update(l, r, c, m + 1, t, (p << 1) | 1);
d[p] = d[p << 1] + d[(p << 1) | 1];
}
int getsum(int l, int r, int s, int t, int p) { // Obtain the answer, as above
if (l <= s && t <= r) return d[p];
int m = s + ((t - s) >> 1);
if (b[p]) {
d[p << 1] = b[p] * (m - s + 1), d[(p << 1) | 1] = b[p] * (t - m);
b[p << 1] = b[(p << 1) | 1] = b[p];
b[p] = 0;
}
int sum = 0;
if (l <= m) sum = getsum(l, r, s, m, p << 1);
if (r > m) sum += getsum(l, r, m + 1, t, (p << 1) | 1);
return sum;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin >> n;
for (int i = 1; i <= n; i++) std::cin >> a[i];
build(1, n, 1);
int q, i1, i2, i3, i4;
std::cin >> q;
while (q--) {
std::cin >> i1 >> i2 >> i3;
if (i1 == 0)
std::cout << getsum(i2, i3, 1, n, 1) << std::endl;
else
std::cin >> i4, update(i2, i3, i4, 1, n, 1);
}
return 0;
}
2018 Multi-University Training Contest 5 Problem G. Glad You Came
Solution Idea
Maintain a persistent tag for each interval, then run DFS on the segment tree to count the answer. Add a pruning optimization when attaching tags; otherwise, it will TLE.
Extensions
Segment trees have very broad applications. Common extensions and variants include:
- Persistent Segment Tree
- Various tree-of-trees structures:
- Segment Tree over Segment Tree
- Fenwick Tree over Segment Tree
- Segment Tree over a Balanced Tree
- Balanced Tree over Fenwick Tree
- Li Chao Segment Tree
- Cat Tree
- Segment Tree Beats
See the corresponding pages for details.
Application: Optimizing Graph Construction with Segment Trees
When building graph edges, we sometimes encounter a problem in which one vertex is connected to every vertex in a contiguous interval, or every vertex in a contiguous interval is connected to one vertex. Connecting the edges one by one makes the complexity explode when there are many vertices, so we use this interval properties of segment trees to optimize graph construction.
Here is a segment tree.
Every node represents an interval. Suppose we want to add edges to interval .
In some problems, an interval points to a single vertex. We can simply reverse every directed edge in the first diagram above. The upper tree is called the in-tree, and the lower one the out-tree.
Legacy
Problem statement: there are vertices and operations. Each operation is one of the following three types:
- Operation 1: add a directed edge with weight .
- Operation 2: for every , add a directed edge with weight .
- Operation 3: for every , add a directed edge with weight .
Find the shortest paths from vertex to all other vertices.
.
Reference Code
#include <bitset>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
using ll = long long;
constexpr int N = 1e5 + 5;
using pil = pair<int, ll>;
using pli = pair<ll, int>;
int n, q, s, tot, rt1, rt2;
int pos[N];
ll dis[N << 3];
vector<pil> e[N << 3];
bitset<(N << 3)> vis;
struct seg {
int l, r, lson, rson;
} t[N << 3];
int ls(int u) { // this concept
return t[u].lson;
}
int rs(int u) { // this concept
return t[u].rson;
}
void build(int &u, int l, int r) { // this conceptpointthis concepttree
u = ++tot;
t[u] = seg{l, r};
if (l == r) {
pos[l] = u;
return;
}
int mid = (l + r) >> 1;
build(t[u].lson, l, mid);
build(t[u].rson, mid + 1, r);
e[u].emplace_back(ls(u), 0);
e[u].emplace_back(rs(u), 0);
}
void build2(int &u, int l, int r) { // this conceptpointthis concepttree
if (l == r) {
u = pos[l];
return;
}
u = ++tot;
t[u] = seg{l, r};
int mid = (l + r) >> 1;
build2(t[u].lson, l, mid);
build2(t[u].rson, mid + 1, r);
e[ls(u)].emplace_back(u, 0);
e[rs(u)].emplace_back(u, 0);
}
void add1(int u, int lr, int rr, int v, ll w) { // pointthis conceptintervalthis conceptedge
if (lr <= t[u].l && t[u].r <= rr) {
e[v].emplace_back(u, w);
return;
}
int mid = (t[u].l + t[u].r) >> 1;
if (lr <= mid) {
add1(ls(u), lr, rr, v, w);
}
if (rr > mid) {
add1(rs(u), lr, rr, v, w);
}
}
void add2(int u, int lr, int rr, int v, ll w) { // intervalthis conceptpointthis conceptedge
if (lr <= t[u].l && t[u].r <= rr) {
e[u].emplace_back(v, w);
return;
}
int mid = (t[u].l + t[u].r) >> 1;
if (lr <= mid) {
add2(ls(u), lr, rr, v, w);
}
if (rr > mid) {
add2(rs(u), lr, rr, v, w);
}
}
void dij(int S) {
priority_queue<pli, vector<pli>, greater<pli>> q;
int tot = (n << 2);
for (int i = 1; i <= tot; ++i) {
dis[i] = 1e18;
}
dis[S] = 0;
q.emplace(dis[S], S);
while (!q.empty()) {
pli fr = q.top();
q.pop();
int u = fr.second;
if (vis[u]) continue;
for (pil it : e[u]) {
int v = it.first;
ll w = it.second;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.emplace(dis[v], v);
}
}
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n >> q >> s;
build(rt1, 1, n);
build2(rt2, 1, n);
for (int i = 1, op, u; i <= q; ++i) {
cin >> op >> u;
if (op == 1) {
int v;
ll w;
cin >> v >> w;
e[pos[u]].emplace_back(pos[v], w);
} else if (op == 2) {
int l, r;
ll w;
cin >> l >> r >> w;
add1(rt1, l, r, pos[u], w);
} else {
int l, r;
ll w;
cin >> l >> r >> w;
add2(rt2, l, r, pos[u], w);
}
}
dij(pos[s]);
for (int i = 1; i <= n; ++i) {
if (dis[pos[i]] == 1e18) {
cout << "-1 ";
} else {
cout << dis[pos[i]] << ' ';
}
}
return 0;
}