Sitan Chen home

Segment Tree

19 Oct 2025

1. Introduction

2. Principle and Implementation

A segment tree is a binary tree that is complete at every level except, possibly, the lowest. Each node represents an interval, while the leaves represent individual elements. A parent is computed from its children, for example by summing them or taking their maximum.

p1
Figure 1

As Figure 1 shows, the segment tree represents the array [10, 11, 12, 13, 14], with each parent equal to the sum of its children.

In a binary tree, the left child of node n is numbered 2n and the right child is numbered 2n+1.

2.1 Building the Tree

void build(int p, int pl, int pr) { // Node p covers [pl, pr]
        if(pl == pr) {
                    tree[p] = a[pl]; // Leaf node
                    return;
        }
        int mid = (pl + pr) / 2;
        build(2*p, pl, mid);
        build(2*p+1, mid+1, pr);
        tree[p] = tree[2*p] + tree[2*p+1]; // Merge children
}

Use recursion starting from the root. Each call passes the current node number and its interval to the next level, so invoke it as follows.

build(1, 1, n);

Stop when the recursion reaches a leaf: its interval has length 1, so its left and right endpoints are equal. At that point, assign the leaf value.

Otherwise recurse into both children, passing their respective numbers and intervals.

The assignment to the current node follows the two recursive calls. When those calls return, both children have been computed, so their values can be combined.

2.2 Range Queries

// p: node; [pl, pr]: node range; [l, r]: target range
int query(int p, int pl, int pr, int l, int r) {
        if(pl >= l && pr <= r) return tree[p];
        int mid = (pl + pr) / 2;	
        int sum = 0;
        if(mid >= l) sum += query(2*p, pl, mid, l, r);
        if(mid+1 <= r) sum += query(2*p+1, mid+1, pr, l, r);
        return sum; 
}

Queries also use recursion and search downward from the root. Invoke one as follows.

query(1, 1, n, l, r);

If the current node’s interval is contained in the target interval, there is no need to search further: return the node’s value directly. This is where a segment-tree range query saves time.

Continue recursively after checking the relationship between the current interval and the target interval; the two if statements do this. Here, mid is the midpoint of the current interval.

If l ≤ mid, the target overlaps interval [pl, mid], which must therefore be searched, as Figure 2 shows.

p2
Figure 2

If mid < r, the target overlaps interval [mid+1, pr], which must therefore be searched, as Figure 3 shows.

p3
Figure 3

2.3 Point Updates

// Add k to element x in node p's range [pl, pr]
void update(int p, int pl, int pr, int x, int k) {
        if(pl == pr) {
                tree[p] += k;
                return ;
        }
        int mid = (pl + pr) / 2;
        if(x <= mid) update(p*2, pl, mid, x, k);
        if(x >= mid+1) update(p*2+1, mid+1, pr, x, k);
        tree[p] = tree[p*2] + tree[p*2+1];
}

Fundamentally, a point update uses the same recursive structure as building the tree.

2.4 Range Updates and Lazy Propagation

Using point updates for a range update requires traversing every element in that interval, which is too expensive.

This is why we introduce lazy propagation: delay changing a node’s information. A range of length n can be represented by fewer than n segment-tree nodes t[i]. Store the update on those nodes rather than immediately applying it to every child; perform the actual propagation only when a tagged node is visited again.

Implement lazy propagation while searching the tree.

  1. Start searching from the root.
  2. When the current interval is a subset of the target interval, apply the range update to this node: t[p] += (pr - pl + 1) * k, and record tag[p] += k.
  3. As stated above, make the actual change only when a tagged node is visited again. Whenever a node is visited, check its tag. If present, pass the tag to both children and clear it at the current node.
  4. Then compare the intervals and recurse.
void update(int p, int pl, int pr, int l, int r, int k) {	
        if(pl >= l && pr <= r) { // Fully covered
                tag[p] += k;
                tree[p] += (pr - pl + 1) * k;
                return ;
        }
        int mid = (pl + pr) / 2;
        if(tag[p]) {
                tag[2*p] += tag[p];
                tag[2*p+1] += tag[p]; // Push tag down
                tree[2*p] += (mid - pl + 1) * tag[p];
                tree[2*p+1] += (pr - mid) * tag[p]; // Update child sums
                tag[p] = 0;
        }
        if(l <= mid) update(p*2, pl, mid, l, r, k);
        if(r >= mid+1) update(p*2+1, mid+1, pr, l, r, k); // Right child
        tree[p] = tree[p*2] + tree[p*2+1];
}

For example, add 5 to every value in range [3,5] of the array 10,11,12,13,14, as Figure 4 shows.

p4
Figure 4
  1. Starting at the root finds [3,3] and [4,5], corresponding to nodes 5 and 3. Tag those two nodes and update their values.
  2. The recursive calls then return through the upper levels and update their values, such as nodes 1, 2, and 3, by summing their two children.

After the update, the segment tree is as shown in Figure 5:

p5
Figure 5

How is the deferred update reflected? At this point, information in nodes 6 and 7 has not yet been updated, even though they belong to [3,5]. They will be updated the next time they are visited.

For example, query interval [4,4].

  1. On reaching node 3, which represents [4,5], find that it has a tag.
  2. Pass the tag to its two children and update their values.
  3. Clear the tag at the current node.

Passing a tag to nodes 6 and 7 is unnecessary because they are leaves. The generic propagation procedure, however, applies the same rule to every visited tagged node, so the tag is still passed to nodes 6 and 7, as Figure 6 shows.

p6
Figure 6

When using lazy propagation, the query code must also be changed.

int query(int p, int pl, int pr, int l, int r) {
        if(pl >= l && pr <= r) return tree[p]; // Fully covered
        int mid = (pl + pr) / 2;
        if(tag[p]) {
                tag[2*p] += tag[p];
                tag[2*p+1] += tag[p]; // Push tag down
                tree[2*p] += (mid - pl + 1) * tag[p];
                tree[2*p+1] += (pr - mid) * tag[p]; // Update child sums
                tag[p] = 0;
        }
        int sum = 0;
        if(mid >= l) sum += query(2*p, pl, mid, l, r);
        if(mid+1 <= r) sum += query(2*p+1, mid+1, pr, l, r); // Right child
        return sum; 
}

3. Examples

Example 1: Luogu P2357

A straightforward template problem. Range updates and range queries require lazy propagation.

For a point update, treat it as a range whose two endpoints are the same.

Example 2: Luogu P2574

For a binary sequence of length n, an update flips 0 to 1 and 1 to 0; a query asks for the number of ones in a target interval.

Here, array t stores the sum for its interval, namely the number of ones.

For an update, t[x] is the number of ones in its interval. Flipping the interval changes it to t[x] = len - t[x].

For tag, flipping an interval twice cancels itself. Let tag[x] = 1 mean a flip is pending and tag[x] = 0 mean none is pending. On each update, use tag[x] = !tag[x].

Finally, note that the input is a string and should be handled accordingly.

// Update and query code
void update_tag() {
  	tag[p*2] = !tag[p*2];
	tag[p*2+1] = !tag[p*2+1]; // Key difference
	tree[2*p] = (mid - pl + 1) - tree[2*p];
	tree[2*p+1] = (pr - mid) - tree[2*p+1]; // Key difference
	tag[p] = 0;
}
void update(int p, int pl, int pr, int l, int r) {
	if(pl >= l && pr <= r) {
		tag[p] = !tag[p]; // Key difference
		tree[p] = (pr - pl + 1) - tree[p]; // Key difference
		return ;
	}
	int mid = (pl + pr) / 2;
	if(tag[p]) update_tag();
	recursion1();
}

int query(int p, int pl, int pr, int l, int r) {
	if(pl >= l && pr <= r) return tree[p];
	if(tag[p]) update_tag();
	recursion2();
}

Example 3: Luogu P1198

A simple variation: replace summation with taking the maximum.

Total visits to this site: times