Lazy Propagation in Segment Tree
Palavras-chave:
Publicado em: 11/09/2025Lazy Propagation in Segment Trees
This article delves into the concept of lazy propagation in segment trees, a powerful technique used to efficiently perform range updates on arrays. We'll explore the underlying principles, provide a comprehensive implementation, and analyze its performance.
Fundamental Concepts / Prerequisites
Before diving into lazy propagation, it's essential to have a solid understanding of the following:
* **Segment Trees:** Familiarity with the basic structure and operation of segment trees, including how to build the tree and perform range queries. * **Range Queries:** Understanding how to retrieve information (e.g., sum, min, max) for a specific range within an array using a segment tree. * **Recursion:** The implementation relies heavily on recursion, so understanding how recursive functions work is crucial.Implementation in C++
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1e5 + 5; // Maximum size of the array
int arr[MAXN]; // Input array
int tree[4 * MAXN]; // Segment tree array
int lazy[4 * MAXN]; // Lazy propagation array
// Function to build the segment tree
void build(int node, int start, int end) {
if (start == end) {
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
build(2 * node, start, mid);
build(2 * node + 1, mid + 1, end);
tree[node] = tree[2 * node] + tree[2 * node + 1]; // Example: Sum segment tree
}
}
// Function to perform lazy propagation
void propagate(int node, int start, int end) {
if (lazy[node] != 0) {
tree[node] += (end - start + 1) * lazy[node]; // Example: Add operation
if (start != end) {
lazy[2 * node] += lazy[node];
lazy[2 * node + 1] += lazy[node];
}
lazy[node] = 0;
}
}
// Function to update a range
void updateRange(int node, int start, int end, int l, int r, int val) {
propagate(node, start, end);
if (start > end || start > r || end < l) {
return;
}
if (start >= l && end <= r) {
lazy[node] += val;
propagate(node, start, end);
return;
}
int mid = (start + end) / 2;
updateRange(2 * node, start, mid, l, r, val);
updateRange(2 * node + 1, mid + 1, end, l, r, val);
tree[node] = tree[2 * node] + tree[2 * node + 1]; // Example: Sum segment tree
}
// Function to query a range
int queryRange(int node, int start, int end, int l, int r) {
propagate(node, start, end);
if (start > end || start > r || end < l) {
return 0; // Neutral element for sum
}
if (start >= l && end <= r) {
return tree[node];
}
int mid = (start + end) / 2;
int p1 = queryRange(2 * node, start, mid, l, r);
int p2 = queryRange(2 * node + 1, mid + 1, end, l, r);
return p1 + p2;
}
int main() {
int n = 5;
for (int i = 0; i < n; ++i) {
arr[i] = i + 1; // Sample array: [1, 2, 3, 4, 5]
}
build(1, 0, n - 1);
updateRange(1, 0, n - 1, 1, 3, 5); // Add 5 to elements from index 1 to 3
cout << "Sum of range [0, 4]: " << queryRange(1, 0, n - 1, 0, 4) << endl; // Expected output: 35 (1 + 7 + 8 + 9 + 5)
return 0;
}
Code Explanation
The code implements a segment tree with lazy propagation for range updates (addition in this case) and range queries (sum). Let's break down each function:
* `build(int node, int start, int end)`: This function constructs the segment tree. It recursively divides the array into halves until it reaches single elements (leaf nodes). Each internal node stores the sum (or other aggregation) of its children's ranges. * `propagate(int node, int start, int end)`: This is the core of lazy propagation. If `lazy[node]` is not 0, it means there's a pending update for the range represented by this node. It applies the update to the current node (`tree[node]`) and propagates the update value to its children's lazy arrays (`lazy[2*node]` and `lazy[2*node + 1]`). Finally, it resets `lazy[node]` to 0. * `updateRange(int node, int start, int end, int l, int r, int val)`: This function updates the range `[l, r]` by adding `val` to each element. It first propagates any pending updates for the current node. Then, if the current node's range is completely outside the update range, it returns. If the current node's range is completely inside the update range, it updates the `lazy` array for that node and propagates. Otherwise, it recursively calls `updateRange` on the left and right children. * `queryRange(int node, int start, int end, int l, int r)`: This function queries the sum of elements in the range `[l, r]`. It first propagates any pending updates for the current node. Then, if the current node's range is completely outside the query range, it returns 0 (the neutral element for addition). If the current node's range is completely inside the query range, it returns the value stored in the current node. Otherwise, it recursively calls `queryRange` on the left and right children and returns the sum of their results. * `main()`: This function demonstrates how to use the segment tree with lazy propagation. It initializes an array, builds the segment tree, performs a range update, and then queries the sum of a range to verify the correctness of the implementation.Complexity Analysis
The time and space complexities of segment trees with lazy propagation are as follows:
* **Time Complexity:** * `build()`: O(N) - Building the segment tree takes linear time. * `updateRange()`: O(log N) - Updating a range takes logarithmic time due to the recursive traversal of the tree. Lazy propagation ensures that updates are applied efficiently. * `queryRange()`: O(log N) - Querying a range also takes logarithmic time. * **Space Complexity:** O(N) - The segment tree and lazy propagation array each require space proportional to the size of the input array.Alternative Approaches
While segment trees with lazy propagation are highly efficient for range updates and queries, alternative approaches exist, though often with trade-offs:
* **Difference Arrays:** Difference arrays can be used for range updates where each element in a range is incremented by a constant value. Updating a range takes O(1) time, but querying the value of a single element requires traversing the difference array up to that element, resulting in O(N) time. Therefore, difference arrays are best suited for scenarios with frequent updates and infrequent queries.Conclusion
Lazy propagation significantly improves the efficiency of segment trees when dealing with range updates. By deferring updates to nodes until they are absolutely necessary, we can reduce the number of operations performed, leading to a logarithmic time complexity for both update and query operations. This makes segment trees with lazy propagation a valuable tool for solving a wide range of problems involving range-based operations on arrays.