线段树专题

线段树说明及理解

  • 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
    • 预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)即2倍空间
  • 未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
  • 区间修改(比如给[l,r]区间所有的值都加上add值的操作),采用lazy标记,即不直接给区间所有的值进行操作。

图解(二分思想)

  • 10个数:3,7,3,2,12,4,7,9,22,1
  • 初始状态
    • root = 1
    • [1, 10]

      其中一号结点为线段树的根节点,最底层一排结点对应的值为我们上述10个数,此时底层结点的下标还不知道。
  • 第一次分解
    • mid = (1+10)/2 = 5
    • root2 = root x 2 = 2, root3 = root x 2+1 = 3
    • [1, 5], [6, 10]
  • 第二次分解

    • mid = (1+5)/2 = 5, mid = (6+10)/2 = 8
    • root4 = root2 x 2 = 4, root5 = root2 x 2+1 = 5
    • root6 = root3 x 2 = 6, root7 = root3 x 2+1 = 7
    • [1, 3],[4,5], [6, 8], [9, 10]
  • 依次类推,最终结果如下图

代码编写:

  • 变量定义
const int maxn = 5e4+7;
int tree[maxn*4]; // 4倍空间
int lazy[maxn*4]; // lazy标记,区间修改用的
  • PushUp函数,用于合并
void PushUp(int root){
    tree[root] = tree[root<<1]+tree[(root<<1)|1];
    //求和 = 左子树+右子树
    // 如果是求最值就是   = max(tree[root<<1],tree[(root<<1)|1)
    return ;
}
  • PushDown函数,lazy处理,用于向下传递lazy值
void PushDown(int root , int l, int r){
    if(!lazy[root]) return ; //为空,无lazy值,无须传递
    int mid = (l+r)>>1; // 二分
    tree[root<<1] += lazy[root]*(mid-l+1); //lazy*区间长度

    tree[(root<<1)|1] += lazy[root]*(r-mid); // lazy*区间长度

    lazy[root<<1] += lazy[root];  // 左子树加上lazy值
    lazy[(root<<1)|1] += lazy[root];//右子树加上lazy值

    lazy[root] = 0; //将当前的根节点的lazy置0,因为lazy已经传递到左右子树了
}
  • 建树build tree
void build(int root, int left, int right){
    if(left == right) {
        scanf("%d", &tree[root]); //到达底层结点
    } else {
        int mid = (left+right) >>1; // x>>1是位运算,表示x除以2

        build(root<<1, left, mid); // 左子树,见图解
        build((root<<1)|1, mid+1, right); // |1是位运算,表示将二进制下的末尾设置为1,在此场景下,即+1操作,右子树,见图解

        // 计算出左右子树的值后就可以计算根节点的值了

        PushUp(root); 

    }
    return ;    
}
//应用:build(1, 1, n);
  • 区间sum查询
//[l, r]表示当前查询区间
//[left, right]表示目标查询区间

int query(int root,int l, int r, int left, int right){
    if(r<left || l>right) return 0; // 不在目标区间范围内直接退出查询
    else if(l>=left && r<=right){ //是目标区间的子区间,直接返回这个区间的值即可
        return tree[root];
    } else {

        // PushDown(root, l, r); 若有区间修改则要调用此函数      

        int mid = (l+r)>>1;

        int ls = query(root<<1, l, mid, left, right); //查询左区间
        int rs = query((root<<1)|1, mid+1, r, left, right);//查询右区间

        return ls+rs;
    }
}
// 应用:query(1, 1, n, x, y);
// x,y表示目标区间
  • 单点更新
// [l, r]表示查询区间
// inx表示更新的下标
// add表示更新值(+或者-)
void update(int root, int l, int r, int inx, int add){
    if(l == r) { // 查找到了该点,更新该点对应的tree[root]值后返回
        tree[root] += add;
        return ;
    }

    int mid = (l+r)>>1;
    // 二分法去查找目标结点
    if(inx <= mid){ 
        update(root<<1, l, mid, inx, add);
    } else {
        update((root<<1)|1, mid+1, r, inx, add);
    }

    // 通过左右子树更新当前root下的值,见图解
    tree[root] = tree[root<<1]+tree[(root<<1)|1];
    return ;
}
//应用:update(1, 1, n, inx, add)
  • 区间更新
//lazy标记 , add表示区间每个元素加上add值(可正负)
void UpdateLR(int root, int l, int r, int left, int right, ll add){
    if(l>right || r < left) return ;

    if(left<=l && r <= right){
        lazy[root] += add;
        tree[root] += add*(r-l+1);
        return ;
    }

    int mid = (l+r) >> 1;
    PushDown(root, l, r);

    UpdateLR(root<<1, l, mid, left, right, add);

    UpdateLR((root<<1)|1, mid+1, r, left, right, add);

    PushUp(root);
}

个人博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值