【线段树】区间加和区间乘的操作并返回区间和

什么是线段树

线段树的思想有点类似于树状数组,都是降低区间和等的操作复杂度。
对于一个正常的序列,很难维护对区间信息,如一个区间的和,区间的最值等问题。但是线段树可以以 O ( l g n ) O(lgn) O(lgn)的复杂度维护这些。

下图是来源与网络的一张线段树的图片,首先我们明确一下线段树的节点的结构。

// l,r 分别表示当前树节点表示的左右区间的边界,val是当前节点所代表的区间的区间信息
// lazytag是这个区间的懒标记。
class Tree{
    
    
    int l = 0;
    int r = 0;
    int val = 0;
    int lazy_tag = 0;
    // 虽然没有方法,但是会有一个默认初始化方法。
}

在这里插入图片描述

线段树类的格式

我们还是及尽量把问题板子化,一个标准的线段树类,可以有以下部分函数构成。

  • SegmentTree函数: 初始化这个类。
  • build函数:递归的方法建立区间树。
  • query函数
  • change函数
  • pushdowupdate函数
    其中完全板子的前四个函数,和需要与维护信息简单修改的方法。

public class SegmentTree {
    
    

    int[] input; // 我们读入的数值
    Tree[] tree;
    // node表示当前节点的编号,r,l表示当前节点代表的区间范围
    // 建树


    public SegmentTree(int n, int[] input){
    
    
        //tree
        tree = new Tree[4*n];
        for (int i = 0; i<4*n; i++){
    
    
            tree[i] = new Tree();
        }
        this.input = input;
    }

	// 在主函数调用的时候一般是build(1, 0, n-1);保证我们从1开始。
    public void build(int x, int l, int r){
    
    
        tree[x].l = l;
        tree[x].r = r;
        if (r == l){
    
    
            tree[x].val = a[l];
            //sum[node] = a[l];
            return;
        }
        int mid = (l+r)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        build(left_node, l, mid);
        build(right_node, mid+1, r);
		// 更新当前节点的数值
        // 如果是最小值,变为min[node] = min(min[left_node]. min(right_node))
        tree[x].val = tree[left_node].val+tree[right_node].val;
    }

    // x 表示当前线段树的节点,[l,r]为要修改的位置,val 为要修改的值
    public void change(int x, int l, int r, int val){
    
    
        if (tree[x].l == l && tree[x].r == r){
    
    
            update(x, val);
            return;
        }
        int mid = (tree[x].r+tree[x].l)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        pushdown(x); // 检查当前是否存在懒标记
        if (r<=mid){
    
     // 说明只需要操作左侧的子树
            change(left_node, l, r, val); // !!!!注意这里是 l,r
        }
        else if (l>=mid+1){
    
    // 说明只需要操作右侧的子树
            change(right_node, l, r, val);
        }else{
    
    
            change(left_node, l, mid, val);
            change(right_node, mid+1, r, val);
        }
        // 在修改完了子节点之后,修改本节点 也可写为pushup函数
        tree[x].val = tree[left_node].val+ tree[right_node].val;
    }

    // node为当前线段树的节点,xy为询问区间和的范围
    public int query(int x, int l, int r){
    
    
        if (tree[x].l == l && tree[x].r == r){
    
    
            return tree[x].val;
        }
        int mid = (tree[x].r+tree[x].l)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        pushdown(x);
        if (r<=mid){
    
    
            return query(left_node, l, r);
        }else if( l>=mid+1){
    
    
            return query(right_node, l,r);
        }else{
    
    
            return query(left_node, l,mid)+query(right_node, mid+1, r);
        }
    }

    // 对于不同的题目,要求维护的区间信息不同,如区间最值,区间和等,修改以下两个函数
    // 重点修改这个函数,这里是求和,且只有一个懒标记
    public void update(int x, int value) {
    
    
        tree[x].val += value*(tree[x].r-tree[x].l+1);
        tree[x].lazy_tag += value;
    }

    public void pushdown(int x){
    
    
        if(tree[x].lazy_tag == 0){
    
    
            return;
        }
        int left_node = 2*x;
        int right_node = 2*x+1;
        update(left_node, tree[x].lazy_tag);
        update(right_node, tree[x].lazy_tag);
        tree[x].lazy_tag = 0;
    }

在例题中使用线段树

板子题1:区间加

在这里插入图片描述

线段树的代码与前面完全一样,这里我主要写一下主函数的代码。

    public static void main(String args[]) throws Exception {
    
    
        int[] input = new int[]{
    
    1,1,1};
        int n = input.length;
        SegmentTree ans = new SegmentTree(n, input);
        // 编号从1,范围0,n-1
        ans.build(1, 0, n-1);
        // 第一个参数不变,永远是1,后面三个参数表示了对[0,2]这个区间上的全部数字+1;
        ans.change(1, 0,2, 1); 
        // 返回答案,第一个参数不变,永远是1,后面表示了对[0,n-1]这个区间的区间和;
        System.out.println(ans.query(1, 0,n-1));

板子题2:区间加和区间乘

在这里插入图片描述

这里就需要两个懒标记了,一个维护乘法一个维护加法的懒标记,每个区间其实应该等价于Y = Ax+B。因此我们先乘乘法懒标记,再加上加法懒标记。

可以参考leectcode1622题

class Tree{
    
    
    int l,r,lazy_tag1,val;
    // 注意乘法的懒标记初始化是1.
    int lazy_tag2 = 1;
}
class SegmentTree{
    
    
    int mod = 1000000007;
    Tree[] tree;
    int[] input;
    public SegmentTree(int N, int[] input){
    
    
        tree = new Tree[N<<2];
        for (int i = 0; i<(N<<2);i++){
    
    
            tree[i] = new Tree();
        }
        this.input = input;
    }

    public void build(int x, int l, int r){
    
    
        tree[x].l = l;
        tree[x].r = r;
        if (l == r){
    
    
            tree[x].val = input[l];
            return;
        }
        int mid = (tree[x].l+tree[x].r)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        build(left_node, l, mid);
        build(right_node, mid+1, r);
        tree[x].val = tree[left_node].val+tree[left_node].val;
    }

    public void change(int x, int l, int r, int val, int type){
    
    
        if (tree[x].l == l && tree[x].r == r){
    
    
            update(x,val, type);
            return;
        }
        int mid = (tree[x].l+tree[x].r)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        pushdown(x);
        if (l>=mid+1){
    
     // 需要修改的部分在右侧
            change(right_node, l, r, val, type);
        }
        else if (r<=mid){
    
    
            change(left_node, l, r, val, type);
        }
        else {
    
    
            change(left_node, l, mid,val, type);
            change(right_node, mid+1, r,val, type);
        }
        tree[x].val = tree[left_node].val+tree[left_node].val;
    }

    public int query(int x, int l, int r){
    
    
        if (tree[x].l == l && tree[x].r == r){
    
    
            return tree[x].val;
        }
        int mid = (tree[x].l+tree[x].r)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        pushdown(x);
        if (l>=mid+1){
    
    
            return query(right_node, l, r);
        }
        else if (r<=mid){
    
    
            return query(left_node, l, r);
        }
        else{
    
    
            return query(left_node, l, mid)+query(right_node, mid+1, r);
        }
    }
	
	// 前面的函数完全一样。下面的不同
    public void update(int x, int val, int type){
    
    
        if (type == 1){
    
     // 表示更新加法懒标记
            tree[x].val = (int)((1L*val*(tree[x].r-tree[x].l+1)+tree[x].val)%mod);
            tree[x].lazy_tag1 = (int)((1L*tree[x].lazy_tag1+val)%mod);;
        }
        else{
    
     // 更新乘法懒标记。
            tree[x].val = (int)((1L*tree[x].val*val)%mod);
            tree[x].lazy_tag2 = (int)((1L*tree[x].lazy_tag2*val)%mod);
            // 注意这个地方加法懒标记也是受到影响的。也会扩大。
            tree[x].lazy_tag1 = (int)((1L*tree[x].lazy_tag1*val)%mod);
        }
    }

    public void pushdown(int x){
    
    
        if (tree[x].lazy_tag1 == 0 && tree[x].lazy_tag2 == 1) return;
        int mid = (tree[x].l+tree[x].r)/2;
        int left_node = 2*x;
        int right_node = 2*x+1;
        // 一定要先update乘法,再update加法。
        update(left_node, tree[x].lazy_tag2,2);
        update(left_node, tree[x].lazy_tag1,1);
        
        update(right_node, tree[x].lazy_tag2,2);
        update(right_node, tree[x].lazy_tag1,1);

        tree[x].lazy_tag1 = 0;
        tree[x].lazy_tag2 = 1;
    }

}

class Fancy {
    
    
    int N = 100000;
    int index = 0;
    int[] a = new int[N];
    SegmentTree st = new SegmentTree(N,a);
    
    public Fancy() {
    
    
        st.build(1, 0, N-1);

    }
    
    public void append(int val) {
    
    
        
        st.change(1, index, index, val,1);
        index++;
    }
    
    public void addAll(int inc) {
    
    
        if (index == 0) return;
        st.change(1, 0, index-1, inc, 1);

    }
    
    public void multAll(int m) {
    
    
        st.change(1, 0, index-1, m, 2);

    }
    
    public int getIndex(int idx) {
    
    
        if (idx >= index) return -1;
        return st.query(1, idx, idx);

    }
}

猜你喜欢

转载自blog.csdn.net/zcz5566719/article/details/109322129