线段树

掌握线段树进行高效的区间查询和更新。学习构建、点/区间更新、懒惰传播和竞赛编程中的高级应用。

1. 基础线段树

用于区间和查询和点更新的线段树,时间复杂度为 O(log n)。

基础线段树实现

#include <iostream>
#include <vector>
using namespace std;

class SegmentTree {
private:
    vector<long long> tree;
    int n;
    
    void build(vector<int>& arr, int node, int start, int end) {
        if (start == end) {
            tree[node] = arr[start];
        } else {
            int mid = (start + end) / 2;
            build(arr, 2*node, start, mid);
            build(arr, 2*node+1, mid+1, end);
            tree[node] = tree[2*node] + tree[2*node+1];
        }
    }
    
    void updateHelper(int node, int start, int end, int idx, int val) {
        if (start == end) {
            tree[node] = val;
        } else {
            int mid = (start + end) / 2;
            if (idx <= mid) {
                updateHelper(2*node, start, mid, idx, val);
            } else {
                updateHelper(2*node+1, mid+1, end, idx, val);
            }
            tree[node] = tree[2*node] + tree[2*node+1];
        }
    }
    
    long long queryHelper(int node, int start, int end, int l, int r) {
        if (r < start || end < l) {
            return 0; // Outside range
        }
        if (l <= start && end <= r) {
            return tree[node]; // Complete overlap
        }
        
        int mid = (start + end) / 2;
        long long p1 = queryHelper(2*node, start, mid, l, r);
        long long p2 = queryHelper(2*node+1, mid+1, end, l, r);
        return p1 + p2;
    }
    
public:
    SegmentTree(vector<int>& arr) {
        n = arr.size();
        tree.resize(4 * n);
        build(arr, 1, 0, n-1);
    }
    
    void update(int idx, int val) {
        updateHelper(1, 0, n-1, idx, val);
    }
    
    long long query(int l, int r) {
        return queryHelper(1, 0, n-1, l, r);
    }
};

int main() {
    vector<int> arr = {1, 3, 5, 7, 9, 11};
    SegmentTree st(arr);
    
    cout << "Original array: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    // Range queries
    cout << "Sum of range [1, 3]: " << st.query(1, 3) << endl;
    cout << "Sum of range [2, 5]: " << st.query(2, 5) << endl;
    
    // Point update
    st.update(1, 10);
    cout << "After updating index 1 to 10:" << endl;
    cout << "Sum of range [1, 3]: " << st.query(1, 3) << endl;
    cout << "Sum of range [0, 5]: " << st.query(0, 5) << endl;
    
    return 0;
}

2. 懒惰传播

懒惰传播用于在 O(log n) 时间复杂度内进行高效的区间更新。

带懒惰传播的线段树

#include <iostream>
#include <vector>
using namespace std;

class LazySegmentTree {
private:
    vector<long long> tree, lazy;
    int n;
    
    void build(vector<int>& arr, int node, int start, int end) {
        if (start == end) {
            tree[node] = arr[start];
        } else {
            int mid = (start + end) / 2;
            build(arr, 2*node, start, mid);
            build(arr, 2*node+1, mid+1, end);
            tree[node] = tree[2*node] + tree[2*node+1];
        }
    }
    
    void updateLazy(int node, int start, int end) {
        if (lazy[node] != 0) {
            tree[node] += lazy[node] * (end - start + 1);
            if (start != end) {
                lazy[2*node] += lazy[node];
                lazy[2*node+1] += lazy[node];
            }
            lazy[node] = 0;
        }
    }
    
    void updateRange(int node, int start, int end, int l, int r, int val) {
        updateLazy(node, start, end);
        if (start > r || end < l) {
            return;
        }
        
        if (start >= l && end <= r) {
            tree[node] += val * (end - start + 1);
            if (start != end) {
                lazy[2*node] += val;
                lazy[2*node+1] += val;
            }
            return;
        }
        
        int mid = (start + end) / 2;
        updateRange(2*node, start, mid, l, r, val);
        updateRange(2*node+1, mid+1, end, l, r, val);
        updateLazy(2*node, start, mid);
        updateLazy(2*node+1, mid+1, end);
        tree[node] = tree[2*node] + tree[2*node+1];
    }
    
    long long queryRange(int node, int start, int end, int l, int r) {
        if (start > r || end < l) {
            return 0;
        }
        updateLazy(node, start, end);
        if (start >= l && end <= r) {
            return tree[node];
        }
        
        int mid = (start + end) / 2;
        long long p1 = queryRange(2*node, start, mid, l, r);
        long long p2 = queryRange(2*node+1, mid+1, end, l, r);
        return p1 + p2;
    }
    
public:
    LazySegmentTree(vector<int>& arr) {
        n = arr.size();
        tree.resize(4 * n);
        lazy.resize(4 * n);
        build(arr, 1, 0, n-1);
    }
    
    void update(int l, int r, int val) {
        updateRange(1, 0, n-1, l, r, val);
    }
    
    long long query(int l, int r) {
        return queryRange(1, 0, n-1, l, r);
    }
};

int main() {
    vector<int> arr = {1, 3, 5, 7, 9, 11};
    LazySegmentTree lst(arr);
    
    cout << "Original array: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    cout << "Sum of range [1, 3]: " << lst.query(1, 3) << endl;
    
    // Range update: add 10 to range [1, 3]
    lst.update(1, 3, 10);
    cout << "After adding 10 to range [1, 3]:" << endl;
    cout << "Sum of range [1, 3]: " << lst.query(1, 3) << endl;
    cout << "Sum of range [0, 5]: " << lst.query(0, 5) << endl;
    
    // Another range update
    lst.update(2, 4, 5);
    cout << "After adding 5 to range [2, 4]:" << endl;
    cout << "Sum of range [2, 4]: " << lst.query(2, 4) << endl;
    
    return 0;
}

3. 高级应用

高级线段树应用,包括区间最小/最大查询和坐标压缩。

区间最小查询 (RMQ) 线段树

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

class RMQSegmentTree {
private:
    vector<int> tree;
    int n;
    
    void build(vector<int>& arr, int node, int start, int end) {
        if (start == end) {
            tree[node] = arr[start];
        } else {
            int mid = (start + end) / 2;
            build(arr, 2*node, start, mid);
            build(arr, 2*node+1, mid+1, end);
            tree[node] = min(tree[2*node], tree[2*node+1]);
        }
    }
    
    void updateHelper(int node, int start, int end, int idx, int val) {
        if (start == end) {
            tree[node] = val;
        } else {
            int mid = (start + end) / 2;
            if (idx <= mid) {
                updateHelper(2*node, start, mid, idx, val);
            } else {
                updateHelper(2*node+1, mid+1, end, idx, val);
            }
            tree[node] = min(tree[2*node], tree[2*node+1]);
        }
    }
    
    int queryHelper(int node, int start, int end, int l, int r) {
        if (r < start || end < l) {
            return INT_MAX;
        }
        if (l <= start && end <= r) {
            return tree[node];
        }
        
        int mid = (start + end) / 2;
        int p1 = queryHelper(2*node, start, mid, l, r);
        int p2 = queryHelper(2*node+1, mid+1, end, l, r);
        return min(p1, p2);
    }
    
public:
    RMQSegmentTree(vector<int>& arr) {
        n = arr.size();
        tree.resize(4 * n);
        build(arr, 1, 0, n-1);
    }
    
    void update(int idx, int val) {
        updateHelper(1, 0, n-1, idx, val);
    }
    
    int query(int l, int r) {
        return queryHelper(1, 0, n-1, l, r);
    }
};

int main() {
    vector<int> arr = {1, 3, 2, 7, 9, 11};
    RMQSegmentTree rmq(arr);
    
    cout << "Array: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    cout << "RMQ(1, 3): " << rmq.query(1, 3) << endl;
    cout << "RMQ(2, 5): " << rmq.query(2, 5) << endl;
    cout << "RMQ(0, 5): " << rmq.query(0, 5) << endl;
    
    rmq.update(2, 0);
    cout << "After updating index 2 to 0:" << endl;
    cout << "RMQ(1, 3): " << rmq.query(1, 3) << endl;
    cout << "RMQ(0, 5): " << rmq.query(0, 5) << endl;
    
    return 0;
}

总结

  • 基础线段树:在 O(log n) 时间内进行高效的区间查询和点更新
  • 懒惰传播:通过延迟计算进行区间更新优化
  • 区间最小/最大:可适应不同的结合运算
  • 应用:区间和、RMQ、坐标压缩和区间问题
  • 空间复杂度:存储树结构需要 O(4n) 空间