1.1 线段树的基础操作

本篇对应的是luogu的线段树1

概况:

如下图就是一棵线段树,线段树上的每一个点记录的都是一个区间,所以线段树支持对于区间和点的动态操作,可以在线查询和更改区间上的最值,求和等

时间复杂度:O(n)

         

使用线段树的情况

  满足区间加法:已知左右两子树的全部信息,一定能够推出父节点

线段树维护的内容根据题目的要求而定

线段树的分类

根据题目中对于查询和修改区间的不同要求,大致将线段树分为三类:

  problem1:单点修改,单点查询

  problem2:区间修改,单点查询

  problem3:区间修改,区间查询

思路:

思路+代码阅读(注:不同题目线段树维护的内容不同,这里给出区间和写法,如果要维护其他内容,只需把下面所有提及sum的内容更改为新的转移方程即可):

001:建立线段树  利用二分,不断维护最终要求的值,返回条件为找到叶节点O(n)

      //构造一棵a1-an的线段树  调用build_xdtree(1,1,a)

      void build_xdtree(int o,int l,int r){

      //o是当前这段区间的点编号, sum[o]用于记录区间和,l是左节点,r是右节点

         if(r==l){     //如果已经找到叶节点,它的区间和就是它自己的值

              sum[o]=a[l];   //a[l]用于记录每个点本身的值

             return;   //找到叶节点,返回

         }

         int mid=(l+r)>>1;   //从中间二分

           build_xdtree(o<<1,l,mi);  //分别build它的左右节点

         build_xdtree(o<<1+1,mid+1,r);

         sum[o]=sum[o<<1]+sum[o<<1+1]; //它的区间和即左右区间和的和

      }

002:PROB1 单点修改和单点查询

    单点修改:O(log2(n))

从根节点开始找,判断x在左右子树

最后更新被影响到的值(和建树相同),条件为叶节点(x)

      //线段树点的更新 如果更新a[x]=y

      void uponedate(into,intl,intr,intx,int y){

       if(l==r){            //找到根节点,也就是x

            sum[o]=y;//更新根节点为新的y

             return;         //返回

           }

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

          if(x<=mid){                   //如果x在o的左子树里

         update(o<<2,l,mid,x,y);   //在左子树里继续找,右子树的值不受影响

        }

      else update(O<<2+1,mid+1,r,x,y);

      //反之在右节点里更新 左节点的值不受影响

         sum[o]=sum[o<<2]+sum[o<<2+1];

      //更新sum[o]的值为更新过后的左右子树之和

      }

  单点查询O(log2(n))

从根节点开始找,终止条件为覆盖区间,分别判断左右子树有没有包含区间一部分

 

      //在只修改点情况下线段树的查询区间ax-ay ans用于记录结果

      void onequery(int o,int l,int r,int x,int y){

         if(x<=l&&y>=r){//思考:为什么这样写

           ans+=sum[o];//对于覆盖区间[l,r]的[x,y],ans直接加上[l,r]的sum

              return;//实际上就等同于找到了完全相等的区间,可以不用向下搜了

        }

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

       if(x<=mid){      //如果有一部分在[l,r]的左子树里,就在左子树里搜索

            query(o<<1,l,mid,x,y)

         }

        if(y>mid){        //如果有一部分在[l,r]的右子树里,就在右子树里搜索

              query(O<<1+1,mid+1,r,x,y);

        }

      }

003:lazy_tag(重点)

对于区间的修改,可以定义一个lazytag,给这个区间打上标记来代替直接对它进行操作,那么在读取或进行其他操作的时候就可以一起读出lazytag,优化了时间复杂度,注意:假设a[o]有一个大小为k的tag,意味着它的子节点都欠着一个tagk没有加。但是a[o]不欠,它的值在调用更改函数时(或者由它的父亲传下来时),就已经更新过了。所以对于a[o]而言,决定它大小的是它的父亲的tag。

push up: 读完tag之后,所有的儿子会更新它的数值(可能是最值、区间和等),那么他们的父亲也会随之更新数值,所以在所有的递归改变了儿子之后,都要调用pushup来更新父亲

      // 如果一个子节点加了x,那么它的父节点也要加上x 在每次递归完左右儿子后调用

      void pushup(int o){

        sum[o]=sum[o<<2]+sum[o<<2+1];

      }

push down:父亲节点的tag会对子节点的数值造成影响,所以在所有的递归儿子之前,为了准确计算儿子的值,都要把父亲的tag传递给它的子节点,并更新子节点的值,同时可以清除父亲的tag,因为他的子节点更新了

      //  将父节点的tag进行下传 在每次递归左右儿子前调用

      void pushdown(int o,int l,int,r,int mid){

        if(tag[o]){//如果父节点的tag不为0

             tag[o<<1+1]+=tag[o];//子节点可能会多次更新tag

          tag[o<<1]+=tag[o];//下传 为了不影响tag的添加

            sum[o<<1]+=tag[o]*(mid-l+1);//节点长度

            sum[o<<1+1]+=tag[o]*(r-mid+1); //更新sum的值

               tag[o]=0;//父节点的tag都下传了 所以更新为0

         }

      }

004:PROB2 区间修改和区间查询

区间修改:

从根节点开始,找到覆盖就打tag,更新值,返回

下传tag,分别判断左右子树有没有包含区间一部分来递归,更新父节点

      //线段树的区间修改 修改内容为ax—ay都加v

      void update(int o,int l,int r,int x,int y,int v){//当前搜索到的点

         if(x<=l&&y>=r){//如果[x,y]覆盖了当前找到的[l,r]

             tag[o]+=v;//给区间[l,r]打上tag,那么就不用再找他的孩子

            sum[o]+=(r-l+1)*tag[o];//前面说过,此时要更新sum[o]的值

             return ;//不用找它的孩子,直接返回

         }

       else{  //开始遍历左右子树

             pushdown(o,l,r,(l+r)>>1);

      //遍历前先下传tag,如果不传,它的子节点就还欠债,计算的值就有错误

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

           if(x<=mid){  //如果左子树里有[x,y]的一部分

                  update(o<<1,l,mid,x,y,v);

                }

            if(y>mid){  //如果右子树里有[x,y]的一部分

            update(o<<1+1,mid+1,r,x,y,v)

            }

           pushup(o);//在搜完左右节点以后,更新一下当前节点的信息

         }

      }

区间查询:

      //线段树的查询区间ax-ay 直接返回

      int query(int o,int l,int r,int x,int y){

          if(x<=l&&y>=r){//如果[x,y]覆盖了[l,r]区间,[l,r]区间的值就是答案的一部分

                return sum[o];//因为是int型,直接返回[l,r]的值,不需要向下搜了

           }

       else{

         pushdown(o,l,r,(l+r)/2);//涉及到子节点的值,先打tag

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

           int ans=0;      //记录答案

         if(x<=mid){    //如果有一部分在[l,r]的左子树里

         ans+=query(o<<1,l,mid,x,y)

       }

        if(y>mid){     //如果有一部分在[l,r]的右子树里

               ans+=query(o<<1+1,mid+1,r,x,y);

        }

         return ans;      //最后不需要更新父节点,因为没有改变子节点的大小

        }

      }

005 整理

   思考:

     001 线段树的结构,可以解决的问题类型,时间复杂度

    002 如何建立一棵线段树呢

   003 在写所有关于点的函数时,他们有什么共同点和不同点

      比如:定义内容,终止条件,更新内容,分类标准,更不更新父节点

   004 在写所有关于区间的函数时,他们有什么共同点和不同点

        比如:定义内容,终止条件,更新内容,分类标准,下传tag,更不更新父节点

    回忆上面内容中标红(细节)和下划线的地方(结构),尝试还原算法

              ---END---

第一版:2019-10-02

原文地址:https://www.cnblogs.com/ray-dexter/p/11617703.html

时间: 2024-10-11 10:15:05

1.1 线段树的基础操作的相关文章

HDU1166 线段树(最基础题)

1.写法一: 1 #include <iostream> 2 #include <string.h> 3 #include <stdio.h> 4 5 using namespace std; 6 7 int numv[50005<<2]; 8 int A[50005]; 9 10 void builtTree(int o,int l,int r){ 11 if(l==r) { 12 numv[o]=A[l]; 13 return ; 14 } 15 int

线段树区间更新操作及Lazy思想(详解)

此题题意很好懂:  给你N个数,Q个操作,操作有两种,‘Q a b ’是询问a~b这段数的和,‘C a b c’是把a~b这段数都加上c. 需要用到线段树的,update:成段增减,query:区间求和 介绍Lazy思想:lazy-tag思想,记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率. 在此通俗的解释我理解的Lazy意思,比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果

UVa 11992 Fast Matrix Operations(线段树双懒操作,二维转一维)

题意:给个r*c的矩形,三种操作,将一个子矩形权值+v,将一个子矩阵权值赋值为v,查询一个子矩阵sum,max,min.起初矩阵权值为0,保证r<=20,r*c<=1e6,操作次数不超过10000 链接: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18697 题解:将二维转一维,设当前点为(x,y),则它在线段树上的点为(x-1)*c+y,由于行不超过20行,不妨枚举每一行,去操作.对于一维的线段树,要进行赋值,加法

线段树的基础递归的使用

问题描述 给定n个数列,规定有两种操作,一是修改某个元素,二是求子数列[a,b]的连续和.数列的元素个数最多100000个,询问操作最多100000次. 输入 第一行2个整数n,m(n表示输入n个数列,m表示有m个操作)第二行输入n个数列. 接下来M行,每行有三个数k,a,b(k=0表示求子数列[a,b]的和,k=1表示第a个数列加b) 输出 输出若干行数字,表示每次K=0时对应输出一个子数列[a,b]的连续和. 输入样列 10 5 1 2 3 4 5 6 7 8 9 10 1 1 5 0 1

poj 3667 Hotel (线段树的合并操作)

Hotel The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a vacation on the sunny shores of Lake Superior. Bessie, ever the competent travel agent, has named the Bullmoose Hotel on famed Cumberland Street as t

CodeForces 620E New Year Tree(线段树的骚操作第二弹)

The New Year holidays are over, but Resha doesn't want to throw away the New Year tree. He invited his best friends Kerim and Gural to help him to redecorate the New Year tree. The New Year tree is an undirected tree with n vertices and root in the v

山海经 (线段树高阶操作)

前几天看mike的ppt发现有线段树的题,就挑了第一道题搞搞吧,然后就gg了,花了三天时间总算搞掉了 先放题: 775. 山海经 ★★★☆   输入文件:hill.in   输出文件:hill.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] “南山之首日鹊山.其首日招摇之山,临于西海之上,多桂,多金玉.有草焉,其状如韭而青华,其名日祝余,食之不饥……又东三百里,日堂庭之山,多棪木,多白猿,多水玉,多黄金. 又东三百八十里,日猨翼之山,其中多怪兽,水多怪鱼,多白玉,

线段树- 算法训练 操作格子

问题描述 有n个格子,从左到右放成一排,编号为1-n. 共有m次操作,有3种操作类型: 1.修改一个格子的权值, 2.求连续一段格子权值和, 3.求连续一段格子的最大值. 对于每个2.3操作输出你所求出的结果. 输入格式 第一行2个整数n,m. 接下来一行n个整数表示n个格子的初始权值. 接下来m行,每行3个整数p,x,y,p表示操作类型,p=1时表示修改格子x的权值为y,p=2时表示求区间[x,y]内格子权值和,p=3时表示求区间[x,y]内格子最大的权值. 输出格式 有若干行,行数等于p=2

线段树的复杂操作

一,线段树做区间乘法 首先要明白,乘法操作高于加法操作 一般的话会开long long ,要去模 对于一个节点o,我们设区间和为sum[o],加法标记为add[o],乘法标记为mul[o] mul标记的初始值是1,add标记初始值是0 在修改值的时候,add的维护需要累加,mul的维护需要累乘 此时当我们进行区间加的时候,一切照旧 但是当进行区间乘法的时候 儿子节点的add标记分别先乘上父亲节点的乘标记再加上父亲节点的加标记 (因为父亲节点的加标记已经乘上了一些乘标记了,不需要再乘一次) 儿子节