线段树的简单介绍

线段树属于完全二叉树,单个数据存在叶子节点中,非叶子节点存储区域内最大值,区间和。

如下是插入[1, 2, 3, 4, 5, 6, 7]后的结果

建树

比如,有n个数据,调用 build [1, n],那么叶子节点就有n个数据,每个节点的左右边界都会如上图。当左右边界相等时,说明到了叶子节点,可以结束这一过程了。建树过程只需要设置左右边界。其他数据可以不用管。

插入数据

从根节点出发,找到左右边界相等的目标叶子节点,沿途需要更新最大值,这个非常好理解。更新区间和,这个也比较好理解,插入数据阶段不需要考虑区间和变少的过程(变少的过程会在更新数据中得以解决),因为插入过程是挨个往叶子节点存放数据的,区间和只会增大,沿途的节点都更新区间和。

更新数据

这个过程类似插入数据的过程,不同之处在于,此过程会考虑区间和“变小”的过程,比如9变成-9,那么沿途区间包含目标节点的节点的区间和,最大值,都需要更新。

区间求和

对于【1,4】这种区间,某节点的左右区间刚好为所求,那么找到这个节点就行,但是存在【2,3】这种“跨节点”的区间,所以使得区间求和变得复杂了一点点。但是通过递归调用,找到“合适的”区间组合,随即完成区间求和。

区间求最大值

对于【1,4】这种区间,某节点的左右区间刚好为所求,那么找到这个节点就行,但是存在【2,3】这种“跨节点”的区间,所以使得区间求和变得复杂了一点点。但是通过递归调用,找到“合适的”区间组合,随即完成区间求最大值。

区间修改

参考自,算法学习笔记(14): 线段树 - 知乎

线段树的区间修改是其精髓所在。

比如,将[1, 9]中的每个元素都加+10,如果是挨个挨个按照单点更新的方式,时间复杂度就是nlogn了,这还是只是一次区间更新,如果是m次,那么时间复杂度则是m * nlogn。显然是不行的。需要用到区间修改。并且再考虑一个问题,第一次,更新[1, 9],第二次更新还是[1, 9],那不是重复操作了吗?有没有一种“去重”,或者延迟更新(等到查询到那一块区域才更新)的操作。

那么就是引入懒标记了,懒标记就有“去重”和延迟更新的功能。

懒标记的过程是:当进行区间修改的时候,先标记着,不实际更新,等到查询的时候,才更新数据,当更新数据的时候,把向下传递的节点的懒标记清除。那么如何标记呢?

有三种情况。

当前区间[3, 4] 包含于要修改的区间 [3, 7],在[3, 4]这个节点做一个标记。

当前区间[3, 4] 不相交 区间 [5, 7],跳过。

当前区间[2,4]  外交于 区间[4,7] ,此时,将区间一分为二,左子树判断,右子树判断(这里不断的递归,最终会走到1,2种情况)。

Java版本-模板-改进(超时,频繁的新建对象,导致超时,时间复杂度应当是没问题,有时间写一个数组的版本)


    public static class TreeNode {
        int val = 0, l = 0, r = 0;
        int sum = 0, max = 0, lazy = 0;

        TreeNode left;
        TreeNode right;

        TreeNode() {}
        TreeNode(int val) { this.val = val; }
        TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }

    public void build(TreeNode node, int l, int r) {
        node.l = l;
        node.r = r;

        if (l == r) return;

        node.left = new TreeNode(0);
        node.right = new TreeNode(0);

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

        build(node.left, l, mid);
        build(node.right, mid + 1, r);
    }

    public void insert(TreeNode node, int index, int value) {
        node.sum += value;
        node.max = Math.max(node.max, value);
        if (node.l == node.r) {
            node.val = value;
            return;
        }

        if (node.left.r <= index) {
            insert(node.left, index, value);
        } else {
            insert(node.right, index, value);
        }
    }

    // 单点更新
    public void update(TreeNode node, int index, int value) {
        if (node.l == node.r) {
            node.max = node.sum = node.val = value;
            return;
        }

        if (node.left.r >= index) {
            update(node.left, index, value);
        } else {
            update(node.right, index, value);
        }
        node.sum = node.left.sum + node.right.sum;
        node.max = Math.max(node.left.max, node.right.max);
    }

    // 区间更新

    public void updateArea(TreeNode node, int l, int r, int k) {
        if (node.l > r || node.r < l) {
            // 毫不相干的两个交集
            return;
        } else if (node.l >= l && node.r <= r) {
            // 目标区间包含,当前区间
            node.sum += (node.r - node.l + 1) * k;

            // 如果不是叶子节点,进行懒惰标记
            if (node.r > node.l) {
                node.lazy += k;
            }
        } else {
            //// 目标区间外交当前区间
            //// 左边可能相交部分
            //node.left.lazy += node.lazy;
            //node.right.lazy += node.lazy;
            //
            //// 左子树会用到的区间,外交,说明左子树的右边界一定包含,
            //node.left.sum += node.lazy * (node.left.r - node.l + 1);
            //// 右子树会用到的区间
            //node.right.sum += node.lazy * (node.r - node.left.r);
            //
            //// 清楚标记
            //node.lazy = 0;
            
            // 向下传递
            pushDown(node);
            updateArea(node.left, l, r, k);
            // 右边相交部分
            updateArea(node.right, l, r, k);

            // 更新当前节点的值
            node.sum += node.left.sum + node.right.sum;
        }
    }

    // 向下传递
    public void pushDown(TreeNode node) {
        // 目标区间外交当前区间
        // 左边可能相交部分
        node.left.lazy += node.lazy;
        node.right.lazy += node.lazy;

        // 左子树会用到的区间,外交,说明左子树的右边界一定包含,
        node.left.sum += node.lazy * (node.left.r - node.l + 1);
        // 右子树会用到的区间
        node.right.sum += node.lazy * (node.r - node.left.r);

        // 清除标记
        node.lazy = 0;
    }

    // 区间和
    public int getSum(TreeNode node, int l, int r) {
        if (node.l > r || node.r < l) {
            return 0;
        } else if (node.l >= l && node.r <= r) {
            return node.sum;
        }

        int leftSum = 0;
        //if (node.left.r >= l) {} // 为什么这么写,因为左左子树的右叶子节点更容易满足条件,可以反向想一下不成立的条件
        pushDown(node);
        leftSum += getSum(node.left, l, r);
        //if (node.right.l <= r) {} // 为什么这么写,因为右右子树的左叶子节点更容易满足条件
        leftSum += getSum(node.right, l, r);

        return leftSum;
    }

    public int getMax(TreeNode node, int l, int r) {
        if (node.l >= l && node.r == r) {
            return node.max;
        }
        if (node.l == node.r) {
            return node.val;
        }

        int maxTemp = Integer.MIN_VALUE;
        if (node.left.r >= l) {
            maxTemp = Math.max(maxTemp, getMax(node.left, l, r));
        }

        if (node.right.l <= r) {
            maxTemp = Math.max(maxTemp, getMax(node.right, l, r));
        }
        return maxTemp;
    }

    public void sequence(TreeNode node) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(node);

        while(!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                System.out.print("[ " + cur.l + ", " + cur.r + " ]    ");
                System.out.print("cur : " + cur.val + ", sum : " + cur.sum + "    max : " + cur.max + " |   ");
                if (cur.left != null) queue.add(cur.left);
                if (cur.right != null) queue.add(cur.right);
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Question1 q = new Question1();
        TreeNode head = new TreeNode(0);
        // 验证建树
        q.build(head, 1, 7);
        q.sequence(head);
        System.out.println("===========================");

        // 验证插入
        for (int i = 1; i <= 7; i++) {
            q.insert(head, i, i);
        }
        q.sequence(head);
        System.out.println("===========================");

        // 验证更新
        for (int i = 1; i <= 7; i++) {
            q.update(head, i, -i);
        }
        q.sequence(head);
        System.out.println("===========================");

//        // 验证区域和
        System.out.println(q.getSum(head, 3, 4));
        System.out.println(q.getSum(head, 2, 6));
        System.out.println(q.getSum(head, 4, 7));
        System.out.println(q.getSum(head, 1, 7));
        System.out.println("===========================");
//
//        // 验证区域最大值
        System.out.println(q.getMax(head, 3, 4));
        System.out.println(q.getMax(head, 4, 7));
        System.out.println(q.getMax(head, 2, 6));
        System.out.println(q.getMax(head, 4, 4));
        System.out.println(q.getMax(head, 4, 5));
        System.out.println("===========================");

        // 验证区间修改
        q.updateArea(head, 1, 7, 1);
        System.out.println(q.getSum(head, 3, 4));
        System.out.println(q.getSum(head, 2, 6));
        System.out.println(q.getSum(head, 4, 7));
        System.out.println(q.getSum(head, 1, 7));
        //q.sequence(head);
    }

Java版-模板-初版

public static class TreeNode {
         int val = 0, l = 0, r = 0, sum = 0, max = 0;

         TreeNode left;
         TreeNode right;

         TreeNode() {}
         TreeNode(int val) { this.val = val; }
         TreeNode(int val, TreeNode left, TreeNode right) {
             this.val = val;
             this.left = left;
             this.right = right;
         }
    }
    
    // 建树
    public void build(TreeNode node, int l, int r) {
        node.l = l;
        node.r = r;

        if (l == r) return;

        int mid = (l + r) >> 1;
        node.left = new TreeNode(0);
        node.right = new TreeNode(0);

        build(node.left, l, mid);
        build(node.right, mid + 1, r);
    }
    
    // 插入数据
    public void insert(TreeNode node, int index, int value) {
        node.sum += value;
        node.max = Math.max(node.max, value);
        if (node.l == node.r) {
            node.val = value;
            return;
        }
        int mid = (node.l + node.r) >> 1;
        if (index <= mid) {
            insert(node.left, index, value);
        } else {
            insert(node.right, index, value);
        }
    }
    
    // 更新数据
    public void update(TreeNode node, int index, int value) {
        if (node.l == node.r) {
            node.max = node.sum = node.val = value;
            return;
        }
        int mid = (node.l + node.r) >> 1;
        if (index <= mid) {
            update(node.left, index, value);
        } else {
            update(node.right, index, value);
        }
        node.sum = node.left.sum + node.right.sum;
        node.max = Math.max(node.left.max, node.right.max);
    }
    
    // 区间和
    public int getSum(TreeNode node, int l, int r) {
        if (node.l == l && node.r == r) {
            return node.sum;
        }
        if (node.l == node.r) {
            return node.val;
        }
        int mid = (node.l + node.r) >> 1;
        if (r <= mid) {
            return getSum(node.left, l, r);
        } else if (l >= mid + 1) {
            return getSum(node.right, l, r);
        } else {
            return getSum(node.left, l, mid) + getSum(node.right, mid + 1, r);
        }
    }
    
    // 区间最大值
    public int getMax(TreeNode node, int l, int r) {
        if (node.l == l && node.r == r) {
            return node.max;
        }
        if (node.l == node.r) {
            return node.val;
        }
        int mid = (node.l + node.r) >> 1;
        if (r <= mid) {
            return getMax(node.left, l, r);
        } else if (l >= mid + 1) {
            return getMax(node.right, l, r);
        } else {
            return Math.max(getMax(node.left, l, mid), getMax(node.right, mid + 1, r));
        }
    }

    // 区间最小值
    public int getMin(TreeNode node, int l, int r) {
        if (node.l == l && node.r == r) {
            return node.min;
        }
        if (node.l == node.r) {
            return node.val;
        }
        int mid = (node.l + node.r) >> 1;
        if (r <= mid) {
            return getMin(node.left, l, r);
        } else if (l >= mid + 1) {
            return getMin(node.right, l, r);
        } else {
            return Math.min(getMin(node.left, l, mid), getMin(node.right, mid + 1, r));
        }
    }

    // 层序遍历
    public void sequence(TreeNode node) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(node);

        while(!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                System.out.print("[ " + cur.l + ", " + cur.r + " ]    ");
                System.out.print("cur : " + cur.val + ", sum : " + cur.sum + "    max : " + cur.max + " |   ");
                if (cur.left != null) queue.add(cur.left);
                if (cur.right != null) queue.add(cur.right);
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Question1 q = new Question1();
        TreeNode head = new TreeNode(0);
        // 验证建树
        q.build(head, 1, 7);
        q.sequence(head);
        System.out.println("===========================");

        // 验证插入
        for (int i = 1; i <= 7; i++) {
            q.insert(head, i, i);
        }
        q.sequence(head);
        System.out.println("===========================");

        // 验证更新
        for (int i = 1; i <= 7; i++) {
            q.update(head, i, -i);
        }
        q.sequence(head);
        System.out.println("===========================");

        // 验证区域和
        System.out.println(q.getSum(head, 3, 4));
        System.out.println(q.getSum(head, 4, 7));
        System.out.println(q.getSum(head, 1, 7));
        System.out.println("===========================");

        // 验证区域最大和
        System.out.println(q.getMax(head, 2, 4));
        System.out.println(q.getMax(head, 4, 7));
        System.out.println(q.getMax(head, 1, 7));
        System.out.println(q.getMax(head, 3, 5));
        System.out.println("===========================");
    }

题目:

ProblemE: 操作格子 
 

 Time Limit: 1    Memory Limit: 128 MB Submit  0 Solved  0

 Submit Status Web Board

Description

有n个格子,从左到右放成一排,编号为1-n。
共有m次操作,有3种操作类型:
1.修改一个格子的权值,
2.求连续一段格子权值和,
3.求连续一段格子的最大值。
对于每个2、3操作输出你所求出的结果。

Input

第一行2个整数n,m。
接下来一行n个整数表示n个格子的初始权值。
接下来m行,每行3个整数p,x,y,p表示操作类型,p=1时表示修改格子x的权值为y,p=2时表示求区间[x,y]内格子权值和,p=3时表示求区间[x,y]内格子最大的权值。

Output

有若干行,行数等于p=2或3的操作总数。
每行1个整数,对应了每个p=2或3操作的结果。

Sample Input

4 3
1 2 3 4
2 1 3
1 4 3
3 1 4

Sample Output

6
3

HINT

数据规模与约定

对于20%的数据n <= 100,m <= 200。

对于50%的数据n <= 5000,m <= 5000。

对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。

思路:线段树

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string.h>
#define in(x) scanf("%d",&x)
#define out(x) printf("%d",x)
using namespace std;
typedef long long int ll;
const int maxn=1e5+5;
struct node{
    int l,r,max;
    ll sum;
}a[400010];
void build(int i,int l,int r)//建树 初始化 
{
    int mid=(l+r)>>1;
    a[i].l=l,a[i].r=r;
    a[i].max=0,a[i].sum=0;
    if(l==r) return;//如果左右边距相等则不再构建
    build(2*i,l,mid);//左孩子 范围为l到mid
    build(2*i+1,mid+1,r);//右孩子  范围为mid+1到r 
}
void insert(int i,int v,int value)//往下延申更新sum和max 
{
    a[i].sum+=value;//延申经过的点都加上这个值 
    if(a[i].max<value) a[i].max=value;//更新最大值 
    if(a[i].l==a[i].r) return;//左右边距相等时,不再往下延申()
    int mid=(a[i].l+a[i].r)>>1;
    if(v<=mid){//插入到左孩子 
        insert(2*i,v,value);
    }
    else insert(2*i+1,v,value);//插入到右孩子 
}
void update(int i,int v,int value)//更改 
{
    if(v==a[i].l&&v==a[i].r){//如果左右边距都对应相等
        a[i].sum=value;         // 则找到了对应的value
        a[i].max=value;    
        return;                 
    }
    int mid=(a[i].l+a[i].r)>>1;        
    if(v<=mid){
        update(2*i,v,value);//从左孩子出寻找 
    }
    else update(2*i+1,v,value);//从右孩子中寻找
    a[i].sum=a[2*i].sum+a[2*i+1].sum;//更新区间和
    a[i].max=max(a[2*i].max,a[2*i+1].max);//搜索自上而下,更新自下而上 
}
ll getsum(int i,int l,int r)//求区间和 
{
    if(l==a[i].l&&r==a[i].r) return a[i].sum;//如果左右边距都对应相等,
    int mid=(a[i].l+a[i].r)>>1;                // 则找到了对应的sum
    if(r<=mid){
        return getsum(2*i,l,r);//从左孩子出寻找 
    }
    else if(l>mid){//从右孩子中寻找 
        return getsum(2*i+1,l,r);
    }
    else{
        return getsum(2*i,l,mid)+getsum(2*i+1,mid+1,r);//左右均找 
    }
}
int getmax(int i,int l,int r)
{
    if(l==a[i].l&&r==a[i].r) return a[i].max;//如果左右边距都对应相等,
    int mid=(a[i].l+a[i].r)>>1;                // 则找到了对应的max
    if(r<=mid){
        return getmax(2*i,l,r);//从左孩子出寻找 
    }
    else if(l>mid){//从右孩子中寻找 
        return getmax(2*i+1,l,r);
    }
    else{
        return max(getmax(2*i,l,mid),getmax(2*i+1,mid+1,r));//左右均找 
    }    
}                                
int main()
{
    int n,m,value,a,b,c;
    in(n),in(m);
    build(1,1,n);//每次从1开始往下延申 
    for(int i=1;i<=n;i++){
        in(value);
        insert(1,i,value);//每次依旧是从顶(1)往下延申 
    }
    while(m--){
        in(a),in(b),in(c);
        if(a==1) update(1,b,c);
        else if(a==2) printf("%lld\n",getsum(1,b,c));
        else if(a==3) printf("%d\n",getmax(1,b,c));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值