动态开点线段树

适用场景:

  1. 需要开一棵空间非常大的线段树
  2. 数组下标需要以负数为索引

具体实现:

  1. 我们是一边修改一边动态建立线段树,所以不需要build
  2. 获取左右儿子不再是p * 2 和 p * 2 + 1,而是用两个数组 lson[p] 和 rson[p] 存两个儿子所在位置
  3. 与lazy标记有同样的思想,需要访问儿子时,再动态地为它分配位置,所以大多数儿子都是以虚拟的方式存在,这样就避免了空间浪费

优点:

  1. 解决了空间限制的问题
  2. 无视索引下标的限制,用一个数组元素表示一段区间的值。

例如,求数组下标 -100 到 0 所对应元素的和,query(1, -100, 100, -100, 0),此时 t[1] 表示的是sum(nums[-100:100]),而它的左儿子 t[lson[1]] 表示的就是sum(nums[-100:0]),即所求答案

 题目链接:850. 矩形面积 II 

    这题属实经典

  1. 用动态开点线段树维护扫描线,根节点的值tr[1]就是扫描线被覆盖的总长度
  2. 维护一个tag标记,tag[i] == 1,表示这条线段被完全覆盖,此时当前线段长度tr[p] = r - l + 1,否则就是没有被完全覆盖,tr[p] = tr[lson[p]] + tr[rson[p]]
  3. 在整个过程中不需要懒标记传递
class Solution {
    typedef long long ll;
    ll tr[3500000], tag[3500000], lson[1500000], rson[1500000], cnt = 2, mod = 1e9 + 7;
public:
    void change(ll p, ll l, ll r, ll s, ll t, ll v) {
        if (r < s || l > t) {
            return;
        } else if (s <= l && r <= t) {
            tag[p] += v;
            if (tag[p]) tr[p] = r - l + 1;
            else tr[p] = (tr[lson[p]] + tr[rson[p]]) % mod;
        } else {
            ll m = l + r >> 1;
            if (lson[p] == 0) lson[p] = cnt++;
            if (rson[p] == 0) rson[p] = cnt++;
            change(lson[p], l, m, s, t, v);
            change(rson[p], m + 1, r, s, t, v);
            if (tag[p]) tr[p] = r - l + 1;
            else tr[p] = (tr[lson[p]] + tr[rson[p]]) % mod;
        }
    }
    int rectangleArea(vector<vector<int>>& r) {
        ll res = 0;
        vector<vector<int>> edges;
        for (int i = 0; i < r.size(); i++) {
            edges.push_back({r[i][0], r[i][1] + 1, r[i][3], 1});
            edges.push_back({r[i][2], r[i][1] + 1, r[i][3], -1});
        }
        sort(edges.begin(), edges.end(), [](vector<int>& a, vector<int>& b) {
            return a[0] < b[0];
        });
        ll last = edges[0][0];
        for (int i = 0; i < edges.size(); i++) {
            res = (res + (edges[i][0] - last) * tr[1]) % mod;
            last = edges[i][0];
            change(1, 0, 1e9, edges[i][1], edges[i][2], edges[i][3]);
        }
        return res;
    }
};

 题目链接:220. 存在重复元素 III

本题的官方解法是滑动窗口+有序集合或桶,翻了翻题解,好像我是第一个用动态开点线段树来解题的

class Solution {
    typedef long long ll;
    ll tr[1500000], lson[1500000], rson[1500000], cnt = 2;
public:
    void change(ll p, ll l, ll r, ll x, ll v) {
        if (r < x || l > x) {
            return;
        } else if (l == r && l == x) {
            tr[p] = v;
            return;
        } else {
            ll m = l + r >> 1;
            if (lson[p] == 0) lson[p] = cnt++;
            if (rson[p] == 0) rson[p] = cnt++;
            change(lson[p], l, m, x, v);
            change(rson[p], m + 1, r, x, v);
            tr[p] = tr[lson[p]] + tr[rson[p]];
        }
    }
    ll query(ll p, ll l, ll r, ll s, ll t) {
        if (r < s || l > t) {
            return 0;
        } else if (s <= l && r <= t) {
            return tr[p];
        } else {
            ll m = l + r >> 1;
            if (lson[p] == 0) lson[p] = cnt++;
            if (rson[p] == 0) rson[p] = cnt++; 
            return query(lson[p], l, m, s, t) + query(rson[p], m + 1, r, s, t);
        }
    }
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int d) {
        cnt = 2;
        ll n = nums.size();
        for (ll i = 0; i < n; i++) {
            if (i > k) {
                change(1, -3e9, 3e9, nums[i - k - 1], 0);
            }
            if (query(1, -3e9, 3e9, (ll)nums[i] - d, (ll)nums[i] + d)) {
                return true;
            }
            change(1, -3e9, 3e9, nums[i], 1);
        }
        return false;
    }
};

题目链接:2070. 每一个查询的最大美丽值 

线段树维护区间最大值 

class Solution {
    int tr[3500000], lson[3500000], rson[3500000], cnt = 2;
public:
    
    void change(int p, int l, int r, int x, int v) {
        if (r < x || l > x) {
            return;
        } else if (l == r && l == x) {
            tr[p] = max(tr[p], v);
            return;
        } else {
            int m = l + r >> 1;
            if (lson[p] == 0) lson[p] = cnt++;
            if (rson[p] == 0) rson[p] = cnt++;
            change(lson[p], l, m, x, v);
            change(rson[p], m + 1, r, x, v);
            tr[p] = max(tr[lson[p]], tr[rson[p]]);
        }
    }
    int query(int p, int l, int r, int s, int t) {
        if (r < s || l > t) {
            return 0;
        } else if (s <= l && r <= t) {
            return tr[p];
        } else {
            int m = l + r >> 1;
            if (lson[p] == 0) lson[p] = cnt++;
            if (rson[p] == 0) rson[p] = cnt++; 
            return max(query(lson[p], l, m, s, t), query(rson[p], m + 1, r, s, t));
        }
    }

    vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {
        int n = items.size();
        for (int i = 0; i < n; i++) {
            change(1, 1, 1e9, items[i][0], items[i][1]);
        }
        vector<int> res;
        for (int x : queries) {
            res.push_back(query(1, 1, 1e9, 1, x));
        }
        return res;
    }
};

 题目链接:715. Range 模块

题意大概为给定一个区间,把给定区间全部设为true,或全部设为false,或询问给定区间是否全部为true

  1. 看到区间操作,自然想到线段树
  2. 数据范围1e9,采用动态开点来避免多余的空间消耗
  3. 同时维护两个懒标记,lazy0和lazy1,lazy0[i] == 1表示该区间元素全部为0,lazy1[i] == 1表示该区间元素全部为1
  4. 注意每次添加一个新标记时,之前的标记要全部作废
class RangeModule {
    int tr[3500000], lson[3500000], rson[3500000], cnt = 2, lazy0[3500000], lazy1[3500000];
public:
    RangeModule() {

    }

    void push_down(int p) {
        if (lson[p] == 0) lson[p] = cnt++;
        if (rson[p] == 0) rson[p] = cnt++;
        if (lazy0[p]) {
            lazy0[lson[p]] = 1;
            lazy0[rson[p]] = 1;
            lazy1[lson[p]] = 0;
            lazy1[rson[p]] = 0;
            tr[lson[p]] = 0;
            tr[rson[p]] = 0;
            lazy0[p] = 0;
        }
        if (lazy1[p]) {
            lazy0[lson[p]] = 0;
            lazy0[rson[p]] = 0;
            lazy1[lson[p]] = 1;
            lazy1[rson[p]] = 1;
            tr[lson[p]] = 1;
            tr[rson[p]] = 1;
            lazy1[p] = 0;
        }
    }

    void change(int p, int l, int r, int s, int t, int v) {
        if (r < s || l > t) {
            return;
        } else if (s <= l && r <= t) {
            if (v == 0) {
                lazy0[p] = 1;
                lazy1[p] = 0;
                tr[p] = 0;
            } else {
                lazy0[p] = 0;
                lazy1[p] = 1;
                tr[p] = 1;
            }
        } else {
            int m = l + r >> 1;
            push_down(p);
            change(lson[p], l, m, s, t, v);
            change(rson[p], m + 1, r, s, t, v);
            tr[p] = tr[lson[p]] & tr[rson[p]];
        }
    }

    int query(int p, int l, int r, int s, int t) {
        if (r < s || l > t) {
            return 1;
        } else if (s <= l && r <= t) {
            return tr[p];
        } else {
            int m = l + r >> 1;
            push_down(p);
            return query(lson[p], l, m, s, t) & query(rson[p], m + 1, r, s, t);
        }
    }

    void addRange(int left, int right) {
        change(1, 0, 1e9, left, right - 1, 1);
    }
    
    bool queryRange(int left, int right) {
        return query(1, 0, 1e9, left, right - 1);
    }
    
    void removeRange(int left, int right) {
        change(1, 0, 1e9, left, right - 1, 0);
    }
};

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值