线段树(学习笔记)

前言

提示:这里可以添加本文要记录的大概内容:

线段树是用来维护 区间信息 的数据结构。

线段树可以在 的O(logN)时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。


提示:以下是本篇文章正文内容,下面案例可供参考

一、树的结点定义

class lineTree {
public:
    int l,/*左孩子*/ r/*右孩子*/, lazy/*懒惰标记*/, sum/*区间和*/;
    lineTree(): l(0), r(0), lazy(0), sum(0) {}
};

二、操作

1.建树

//根节点的下标为1
//更新结点区间操作,它的左孩子为2 * i,右孩子为2 * i + 1
inline void update(int i) {
    tree[i].sum = tree[i << 1].sum + tree[(i << 1) | 1].sum;
}

//边界了l,r均表示的是数组的下标
void unit(int i, int l, int r, const vector<int>& data) {
    //当前区间左边界等于右边界,此时结点为叶子结点
    tree[i].l = l, tree[i].r = r;
    if (l == r) {
        tree[i].sum = data[l];
        return;
    }
    int mid = (l + r) >> 1;
    //递归到左孩子,建立左子树,将左半部分区间建立
    unit(i << 1, l, mid, data);
    //递归到右孩子,建立右子树,将右半部分区间建立
    unit((i << 1) | 1, mid + 1, r, data);
    update(i);//因为左右孩子代表的区间和已经更新,所以他们的父节点区间和也要更新
    return;
}

2.单点修改

代码如下:

void singleFix(int k, int val, int i) {
    //当前点为所要修改的点
    if (tree[i].l == tree[i].r && tree[i].l == k) {
        tree[i].sum += val;
        return;
    }
    int mid = (tree[i].l + tree[i].r) >> 1;
    //点k在左区间
    if (mid >= k) singleFix(k, val, i << 1);
    //点k在右区间
    else singleFix(k, val, (i << 1) | 1);
    update(i);
    return;
}

3.区间修改

代码如下(区间加减):

void fixScope(int i, int l, int r, int x) {
    //当前结点表示区间为所要修改的区间
    pushDown(i);
    if (tree[i].l == l && tree[i].r == r) {
        tree[i].sum += (r - l + 1) * x;//当前区间和加上元素个数乘以x
        tree[i].lazy += x; // 不用一个一个更新每个结点区间和,懒惰标记,后续查找利用此值修改
        pushDown(i);
        return;
    }
    int mid = (tree[i].l + tree[i].r) >> 1;
    //被修改区间全部被包含与左孩子结点表示的区间中
    if (mid >= r && tree[i].l <= l) fixScope(i << 1, l, r, x);
    //被修改区间全部被包含与右孩子结点表示的区间中
    else if (mid < l && tree[i].r >= r) fixScope((i << 1) | 1, l, r, x);
    //被修改区间分别分散与左右孩子区间中,即一部分在当前结点区间左边, 一部分在右边
    else {
        fixScope(i << 1, l, mid, x);
        fixScope((i << 1) | 1, mid + 1, r, x);
    }
    update(i);//记住这个!!!
    return;
}

4.查询

int query(int l, int r, int i) {
    //如果当前结点表示的区间为所要查询的区间,返回区间值并加上懒惰标记的内容,在下传标记
    if (tree[i].l == l && tree[i].r == r) return tree[i].sum;
    pushDown(i);
    int sum = 0;
    int mid = (tree[i].l + tree[i].r) >> 1;
    //如果当前结点左孩子表示的区间包括查询区间,查询左孩子
    if (mid >= r) return query(l, r, 2 * i);
    //如果当前结点右孩子表示的区间包括查询区间,查询右孩子
    else if (mid < l) return query(mid + 1, r, 2 * i + 1);
    //查询区间分别分布在两边
    else {
        return query(l, mid, 2 * i) + query(mid + 1, r, 2 * i + 1);
    }
}

4.标记下传(区间加)

void pushDown(int i) {
    if (!tree[i].lazy) return;
    if (tree[i].l == tree[i].r) {
        tree[i].lazy = 0;
        return;
    }
    tree[(i << 1) | 1].sum += (tree[(i << 1) | 1].r - tree[(i << 1) | 1].l + 1) * tree[i].lazy;
    tree[i << 1].lazy = tree[i].lazy;
    tree[(i << 1) | 1].lazy = tree[i].lazy;
    tree[i].lazy = 0;
    return;
}

三、整体代码(区间加)

#include <bits/stdc++.h>
using namespace std;
const int N = 100;

class lineTree {
public:
    int l, r, lazy, sum;
    lineTree(): l(0), r(0), lazy(0), sum(0) {}
};

lineTree tree[N];
//下传标记:先看后面的操作这个后面看
void pushDown(int i) {
    if (!tree[i].lazy) return;
    if (tree[i].l == tree[i].r) {
        tree[i].lazy = 0;
        return;
    }
    tree[(i << 1) | 1].sum += (tree[(i << 1) | 1].r - tree[(i << 1) | 1].l + 1) * tree[i].lazy;
    tree[i << 1].lazy = tree[i].lazy;
    tree[(i << 1) | 1].lazy = tree[i].lazy;
    tree[i].lazy = 0;
    return;
}


//更新结点区间操作,它的左孩子为2 * i,右孩子为2 * i + 1, 位运算优化
inline void update(int i) {
    tree[i].sum = tree[i << 1].sum + tree[(i << 1) | 1].sum;
}


//建树
void unit(int i, int l, int r, const vector<int>& data) {
    //当前区间左边界等于右边界,此时结点为叶子结点
    tree[i].l = l, tree[i].r = r;
    if (l == r) {
        tree[i].sum = data[l];
        return;
    }
    int mid = (l + r) >> 1;
    //递归到左孩子,建立左子树,将左半部分区间建立
    unit(i << 1, l, mid, data);
    //递归到右孩子,建立右子树,将右半部分区间建立
    unit((i << 1) | 1, mid + 1, r, data);
    update(i);//记得要用左右子区间的值更新该区间的值
    return;
}


//单点修改
void singleFix(int k, int val, int i) {
    //当前点为所要修改的点
    if (tree[i].l == tree[i].r && tree[i].l == k) {
        tree[i].sum += val;
        return;
    }
    int mid = (tree[i].l + tree[i].r) >> 1;
    //点k在左区间
    if (mid >= k) singleFix(k, val, i << 1);
    //点k在右区间
    else singleFix(k, val, (i << 1) | 1);
    update(i);
    return;
}


//区间修改:区间所有元素加上x的代码(相对标记)
void fixScope(int i, int l, int r, int x) {
    //当前结点表示区间为所要修改的区间
    pushDown(i);
    if (tree[i].l == l && tree[i].r == r) {
        tree[i].sum += (r - l + 1) * x;//当前区间和加上元素个数乘以x
        tree[i].lazy += x; // 不用一个一个更新每个结点区间和,懒惰标记,后续查找利用此值修改
        pushDown(i);
        return;
    }
    tree[i].sum += (r - l + 1) * x;
    int mid = (tree[i].l + tree[i].r) >> 1;
    //被修改区间全部被包含与左孩子结点表示的区间中
    if (mid >= r && tree[i].l <= l) fixScope(i << 1, l, r, x);
    //被修改区间全部被包含与右孩子结点表示的区间中
    else if (mid < l && tree[i].r >= r) fixScope((i << 1) | 1, l, r, x);
    //被修改区间分别分散与左右孩子区间中,即一部分在当前结点区间左边, 一部分在右边
    else {
        fixScope(i << 1, l, mid, x);
        fixScope((i << 1) | 1, mid + 1, r, x);
    }
    update(i);
    return;
}

//查询区间和(区间修改为加上x),查询其他例如最大值再修改
int query(int l, int r, int i) {
    if (tree[i].l == l && tree[i].r == r) return tree[i].sum;
    pushDown(i);
    int sum = 0;
    int mid = (tree[i].l + tree[i].r) >> 1;
    if (mid >= r) return query(l, r, 2 * i);
    else if (mid < l) return query(mid + 1, r, 2 * i + 1);
    else {
        return query(l, mid, 2 * i) + query(mid + 1, r, 2 * i + 1);
    }
}


int main() {
    int n;
    cin >> n;
    vector<int> a;
    for (int i = 0; i < n; i++) {
        int data;
        cin >> data;
        a.push_back(data);
    }
    
    unit(1, 0, n - 1, a);
    cout << "未修改前1到4的区间和为" << query(1, 4, 1) << endl;
    
    singleFix(3, 4, 1);
    cout << "单点修改后1到4的区间和为(a[3]+4)" << query(1, 4, 1) << endl;
    
    fixScope(1, 2, n - 1, 5);
    cout << "将下标2到" << n - 1 <<"元素全加上5的全部区间和为" << query(0, n - 1, 1) << endl;
    fixScope(1, 0, 3, 5);
    cout << "将0到3元素加上5的区间和(0到4)为" << query(0, 4, 1) << endl;
    
    return 0;
}

动态开点线段树

const int N = 3500000;
class segmentTree{
    int num[N] = {0}, lc[N] = {0}, rc[N] = {0}, cnt = 2, lazy0[N] = {0}, lazy1[N] = {0};
public:
    void pushDown(int p) {
        if(!lc[p]) lc[p] = cnt++;
        if(!rc[p]) rc[p] = cnt++;
        if(lazy0[p]) {
            lazy0[lc[p]] = 1;
            lazy1[lc[p]] = 0;
            lazy0[rc[p]] = 1;
            lazy1[rc[p]] = 0;
            num[lc[p]] = 0;
            num[rc[p]] = 0;
            lazy0[p] = 0;
        }
        else if(lazy1[p]) {
            lazy0[lc[p]] = 0;
            lazy1[lc[p]] = 1;
            lazy0[rc[p]] = 0;
            lazy1[rc[p]] = 1;
            num[lc[p]] = 1;
            num[rc[p]] = 1;
            lazy1[p] = 0;
        }
    }

    void update(int p, int l1, int r1, int L, int R, int val) {
        if(L > r1 || R < l1) return;
        if(L <= l1 && R >= r1) {
            num[p] = val;
            if(val) {
                lazy0[p] = 0;
                lazy1[p] = 1;
                num[p] = 1;
            }else {
                lazy0[p] = 1;
                lazy1[p] = 0;
                num[p] = 0;
            }
            return;
        }
        pushDown(p);
        int mid = (l1 + r1) >> 1;
        update(lc[p], l1, mid, L, R, val);
        update(rc[p], mid + 1, r1, L, R, val);
        num[p] = num[lc[p]] && num[rc[p]];
    }

    bool query(int p, int l1, int r1, int L, int R) {
        if(L > r1 || R < l1) return true;
        if(L <= l1 && R >= r1) {
            return num[p];
        }
        pushDown(p);
        int mid = (l1 + r1) >> 1;
        bool ans = true;
        ans &= query(lc[p], l1, mid, L, R);
        ans &= query(rc[p], mid + 1, r1, L, R);
        return ans;
    }
};

总结

理解清线段树结点表示的含义(表示的区间的两个边界,比如,根节点tree[1]表示数组下标从0到n-1整个数组的和,左右孩子分别代表父节点区间的两半),根据实际修改一下懒标记的内容,在查询和修改和下传标记中修改一下有关使用懒标记的操作。比如将整个区间加上某个值,在修改和查询时就需要加所修改区间长度乘以这个值,然后下传标记;如果将区间全部修改为某个值那这个+=就得变成=。

例题

区间修改某个值的例题:
leetcode LCP52二叉搜索树染色:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值