线段树入门(线段懵逼树)

线段树上懵逼果,线段树下我和我,线段树上找bug,掉发多又多-----------题记


辣鸡张当时学习的博客(如果学到了新的东西或者说有新的理解后期再更新)
xy 写得这篇也挺好的,商业胡吹\sout{胡吹}胡吹互吹链接???
∙\bullet∙线段树常用来处理区间和、区间最大值、区间最小值问题,但是不仅局限于处理区间问题。
∙\bullet∙什摸士线段树:线段树是一棵二叉搜索树。每个结点存储的是一段区间的最大值、最小值或者区间内元素的和。每个结点最多有两个孩子(可以有一个或者两个甚至是个没有孩子的单身大汉)。如果父亲结点的标号是rootrootroot,辣么它的左孩子的标号解就是root∗2root*2root∗2,右孩子的标号就是root∗2+1root*2+1root∗2+1。以下面的线段懵逼树为例:1号结点就存储的是【1,5】区间的和。

∙\bullet∙问题引入:点此打开自闭页给你一个Fucking\sout{Fucking}Fucking​序列a1......ana_1......a_na1​......an​再来qqq次骚操作。
每次操作有两种:“C a b c” means adding c to each of Aa,Aa+1,...,Ab.−10000≤c≤10000.A_a, A_{a+1}, ... , A_b. -10000 ≤ c ≤ 10000.Aa​,Aa+1​,...,Ab​.−10000≤c≤10000.
“Q a b” means querying the sum of Aa,Aa+1,...,Ab.A_a, A_{a+1}, ... , A_b.Aa​,Aa+1​,...,Ab​. So, you need to answer each ‘Q’.
Sample Input

    10 51 2 3 4 5 6 7 8 9 10Q 4 4Q 1 10Q 2 4C 3 6 3Q 2 4

Sample Output

    455915

懵逼少年找了许久bug,终于解决了这个题

∙\bullet∙模板代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int maxn = 1e5 + 10;
LL num[maxn];
LL n,q;
struct node{LL sum,lazy;}tree[maxn*6];void update(LL root){tree[root].sum=tree[root * 2].sum+tree[root * 2 + 1].sum;return ;
}void build(LL root,LL l,LL r){///建树tree[root].lazy = 0;///初始化tree[root].sum = 0;///初始化if(l == r){///如果左端点=又端点就说明走到了叶子节点,此时的sum肯定是叶子节点的值tree[root].sum = num[l];return ;}LL m = (l + r) / 2;build(root*2,l,m);build(root*2+1,m+1,r);update(root);
}void pushdown(LL root,LL l,LL r){if(tree[root].lazy == 0)return ;LL m= (l + r) / 2;LL left = root * 2;LL right = root * 2 + 1;tree[left].lazy += tree[root].lazy;tree[right].lazy += tree[root].lazy;tree[left].sum += tree[root].lazy * (m-l+1);tree[right].sum += tree[root].lazy * (r-m);tree[root].lazy = 0;
}void change(LL root,LL l,LL r,LL ql,LL qr,LL val){if(l >= ql && r <= qr){tree[root].lazy += val;tree[root].sum += (r-l+1) * val;return ;}pushdown(root,l,r);LL m = (l + r) / 2;if(ql <= m)change(root*2,l,m,ql,qr,val);if(qr > m)change(root*2+1,m+1,r,ql,qr,val);update(root);
}LL query(LL root,LL l,LL r,LL ql,LL qr){if(l >= ql && r <= qr){return tree[root].sum;}pushdown(root,l,r);LL m = (l + r) / 2;LL sum = 0;if(ql<=m)sum += query(root*2,l,m,ql,qr);if(qr>m)sum += query(root*2+1,m+1,r,ql,qr);return sum;
}void input(){for(int i = 1;i <= n;i++)scanf("%lld",&num[i]);
}int main()
{while(scanf("%lld%lld",&n,&q)!=EOF){input();build(1,1,n);while(q--){char str[10]={'\0'};scanf("%s",str);if(strcmp(str,"C") == 0){LL a,b,c;scanf("%lld%lld%lld",&a,&b,&c);change(1,1,n,a,b,c);}if(strcmp(str,"Q") == 0){LL a,b;scanf("%lld%lld",&a,&b);LL ans = query(1,1,n,a,b);printf("%lld\n",ans);}}}return 0;
}
∙\bullet∙解释一下build()build()build()、update()update()update()函数:
void update(LL root){tree[root].sum=tree[root * 2].sum+tree[root * 2 + 1].sum;return ;
}
void build(LL root,LL l,LL r){///建树tree[root].lazy = 0;///初始化tree[root].sum = 0;///初始化if(l == r){///如果左端点=又端点就说明走到了叶子节点,此时的sum肯定是叶子节点的值tree[root].sum = num[l];return ;}LL m = (l + r) / 2;build(root*2,l,m);build(root*2+1,m+1,r);update(root);
}

这是一个递归建树的过程,下面就来模拟一下回溯的过程,现在就到了递归的出口,就是左端点=右端点,此时就走到了叶子节点,此时的sum肯定是叶子节点的值,黄色代表已经更新过值的结点,蓝色代表更新完叶子节点后回溯到上一层的结点:

然后就要通过update()update()update()函数更新他们的爸爸4号节点:
更新完爸爸结点后又要回溯:

此时又要去更新2号节点的右儿子5号结点:

5号结点更新完后又回溯到了2号结点

然后就要通过update()update()update()函数更新2号结点:

之后的过程就是这样,直至整棵树构造好。

∙\bullet∙解释一下pushdown()pushdown()pushdown()和change()change()change()函数:

void pushdown(LL root,LL l,LL r){if(tree[root].lazy == 0)return ;LL m= (l + r) / 2;LL left = root * 2;LL right = root * 2 + 1;tree[left].lazy += tree[root].lazy;tree[right].lazy += tree[root].lazy;tree[left].sum += tree[root].lazy * (m-l+1);tree[right].sum += tree[root].lazy * (r-m);tree[root].lazy = 0;
}void change(LL root,LL l,LL r,LL ql,LL qr,LL val){if(l >= ql && r <= qr){tree[root].lazy += val;tree[root].sum += (r-l+1) * val;return ;}pushdown(root,l,r);LL m = (l + r) / 2;if(ql <= m)change(root*2,l,m,ql,qr,val);if(qr > m)change(root*2+1,m+1,r,ql,qr,val);update(root);
}

如果要给一段连续的区间加上某个值ccc(发零花钱哈哈哈),当然可以每次都找到叶子节点然后将它们更新就可以了,但是这样子太费时间了(这样就叫做单点更新,但是我还是喜欢单点更新,因为代码少)。不过有更高级的做法噻:如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间)那就直接让这个结点的sum值乘以区间的长度再乘以ccc就行了撒,那就出大问题了啊,它就十分的贪污了啊,那他的孩子结点呢,就不管了吗。为了解决这个问题,我们引入了lazy标记,就是结构体里的lazy。偷了懒之后,就把lazy的值加上它贪污的值(为甚麽要“加”,直接赋值不就好了吗,不不不,因为它有可能多次贪污哈哈)。

在加上lazy之后,为甚麽有个pushdown()pushdown()pushdown()函数呢,这个函数的作用就是将爸爸的贪污值传递给它的儿子,让他的儿子的sum加上贪污值,并且给它的儿子也标记上lazy值,然后再把自己的lazy值删去(不仅贪污,还让儿子背锅)。你可能会问这不就多此一举吗,不不不。如果题目有多次给一段区间加上某个值,假如有一次给2号结点标记了lazy值,然后下一次又要给4号结点标记lazy值,如果没有pushdown()pushdown()pushdown()函数,是不是2号结点的孩子4号就少了钱,那他的爸爸就真的贪污了。如果有pushdown()pushdown()pushdown()函数,并且pushdown()pushdown()pushdown()函数还是加在递归走左右两个孩子的语句之前,那么在给4号结点标记lazy值之前就把它的爸爸2号的lazy值传递到4号了,这样4号的钱就不会少了。这个pushdown()pushdown()pushdown()函数就是预防贪污。

注意递归走完左右两个孩子之后还要update()update()update()一下。

∙\bullet∙解释一下query()query()query()函数:
LL query(LL root,LL l,LL r,LL ql,LL qr){if(l >= ql && r <= qr){return tree[root].sum;}pushdown(root,l,r);LL m = (l + r) / 2;LL sum = 0;if(ql<=m)sum += query(root*2,l,m,ql,qr);if(qr>m)sum += query(root*2+1,m+1,r,ql,qr);return sum;
}

query()query()query()函数函数里的pushdown()pushdown()pushdown()函数跟上一个是一样的功能,就不多说了。递归出口就是如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间),就直接返回sum值就行了。

∙\bullet∙再贴一个求区间最大值的代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int maxn = 2e5 + 10;
LL num[maxn];
LL n,q;struct node{LL val;}tree[maxn*4];void update(LL root){tree[root].val=max(tree[root*2].val,tree[root*2+1].val);return ;
}void build(LL root,LL l,LL r){tree[root].val=-inf;if(l==r){tree[root].val=num[l];return ;}LL m = ( l + r ) / 2;build(root*2,l,m);build(root*2+1,m+1,r);update(root);
}void change(LL root,LL l,LL r,LL pos,LL val){if(l == r){tree[root].val = val;return ;}int m = ( l + r ) / 2;if(pos <= m)change(root*2,l,m,pos,val);if(pos > m)change(root*2+1,m+1,r,pos,val);update(root);
}int query(LL root,LL l,LL r,LL ql,LL qr){if(l>=ql&&r<=qr){return tree[root].val;}int m = (l + r) / 2;int ans = -inf;if(ql <= m)ans = max(ans,query(root*2,l,m,ql,qr));if(qr > m)ans = max(ans,query(root*2+1,m+1,r,ql,qr));return ans;
}void input(){for(int i=1;i<=n;i++){scanf("%lld",&num[i]);}
}int main()
{while(scanf("%lld%lld",&n,&q)!=EOF){input();build(1,1,n);while(q--){char str[10] = {'\0'};scanf("%s",str);if(strcmp(str,"Q") == 0){LL a,b;scanf("%lld%lld",&a,&b);LL ans=query(1,1,n,a,b);printf("%lld\n",ans);}if(strcmp(str,"U") == 0){LL a,b;scanf("%lld%lld",&a,&b);change(1,1,n,a,b);}}}return 0;
}

线段树入门(线段懵逼树、加了一些解释,丰富了一下内容)相关推荐

  1. 线段树简单入门 (含普通线段树, zkw线段树, 主席树)

    线段树简单入门 递归版线段树 线段树的定义 线段树, 顾名思义, 就是每个节点表示一个区间. 线段树通常维护一些区间的值, 例如区间和. 比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和 ...

  2. 树状数组及线段树入门(SDNU1665-1668)

    目录 前言 树状数组 先导 单点修改区间查询 区间修改区间查询 线段树 先导 单点修改区间查询--递归形式 单点修改区间查询--非递归形式 区间修改区间查询--递归形式 区间修改区间查询--非递归形式 ...

  3. poj1195 Mobile phones 二维线段树入门

    二维线段树就是树套树,线段树套线段树... #include<iostream> #include<cstdio> #include<cstring> #inclu ...

  4. 数据结构之线段树入门(单点更新区间查询)

    线段树是学习数据结构必须学习的一种数据结构,在ACM,蓝桥等比赛中是经常出现的.利用线段树解题,会使得题目简单易理解.而且线段树是数据结构中比较基础而且用的很多的一种. 线段树定义 线段树是一种二叉搜 ...

  5. 线段树入门 (zz)

    从简单说起,线段树其实可以理解成一种特殊的二叉树.但是这种二叉树较为平衡,和静态二叉树一样,都是提前已经建立好的树形结构.针对性强,所以效率要高.这里又想到了一句题外话:动态和静态的差别.动态结构较为 ...

  6. 蒟蒻的线段树入门模板笔记

    线段树适合处理那些问题? 线段树是算法竞赛中常用的用来维护 区间信息 的数据结构. 线段树可以在O(logN)的时间复杂度内实现单点修改.区间修改.区间查询(区间求和,求区间最大值,求区间最小值,求区 ...

  7. 【线段树】线段树及其相关 复习

    划分树: poj2104 K-th number /*******************************\* @prob: poj2104 K-th number ** @auth: Wan ...

  8. 【模板】可持久化线段树 1(主席树)

    题目背景 这是个非常经典的主席树入门题--静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输 ...

  9. 线段树-简单线段树模板

    简单线段树 2021年7月30 线段树是干什么的? 更新,维护,查询某区间的某项值,如区间和等. 线段树的原理 与树状数组类似,线段树通过直接建树来保存区间的结点,如图: 如何建图? 下面以求序列区间 ...

  10. 【luogu3834】【模板】可持久化线段树 2(主席树),静态区间第K小值

    problem solution 题目:有n个数,多次询问一个区间[L,R]中第k小的值是多少. 思路: 查询[1,n]中的第k小值: 先对数据进行离散化,然后按值域建立线段树,线段树中维护某个值域中 ...

最新文章

  1. Zabbix基本配置及监控主机
  2. python如何做一个数据库_Python创建一个新的Django项目(连接到MySQL数据库),python,新建,mysql...
  3. 电力系统单机无穷大_电力系统分析(九):电力系统的稳定性分析
  4. 详解如何使用Istio监控基于容器的服务
  5. Java中集合(三)Stack
  6. Javascript - ExtJs - 组件 - 分页
  7. script标签的同步和异步
  8. 安装开源 ITIL 门户 iTOP
  9. Vue计算属性、方法、侦听器
  10. 加工生产调度(信息学奥赛一本通-T1425)
  11. java修改另存excel_Word文档导入Excel总变形,调整格式你花了1小时,同事三步就搞定...
  12. Hadoop大数据之Debug
  13. 原生指针auto_ptr的用法
  14. android studio 找不到button对象_为什么那么多漂亮(真漂亮)并且条件优秀的女生找不到对象?...
  15. CF1399C Boats Competition
  16. ffmpeg MP3转wav
  17. java 实现点击率_redis实现点击量/浏览量
  18. 蓝桥杯试题算法训练之删除数组零元素——Python满分解答
  19. python|爬虫|爬取豆瓣自己账号下的观影记录并可视化
  20. 如影智能:深度赋能家居行业上下游,共享全屋智能万亿市场红利

热门文章

  1. 基于Android的办公自动化系统APP设计与实现
  2. CRM客户管理系统(Java)
  3. docker-compose 安装常用服务
  4. python 图片文字化处理_Python图像处理之图片文字识别功能(OCR)
  5. Python numpy.ones_like函数方法的使用
  6. Vue解决跨域问题之Node反向代理
  7. 压缩包密码破解工具-ARCHPR
  8. java实现输入数字 输出金额_JAVA实现数字大写金额转换的方法
  9. RAID磁盘阵列与磁盘阵列卡
  10. excel组合汇总_Excel汇总20151102