线段树(Segment Tree)

入门模板题 洛谷oj P3372

题目描述

  如题,已知一个数列,你需要进行下面两种操作:

  1.将某区间每一个数加上x

  2.求出某区间每一个数的和

输入格式

  第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

  第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

  接下来M行每行包含3或4个整数,表示一个操作,具体如下:

  操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

  操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出格式

  输出包含若干行整数,即为所有操作2的结果。

输入样例

5 5

1 5 4 2 3

2 2 4

1 2 3 2

2 3 4

1 1 5 1

2 1 4

输出样例

11

8

20

数据范围

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

对于100%的数据:N<=100000,M<=100000

  才学会写线段树不久……随便扯一篇笔记orz

0x00 线段树概念

  线段树是一种二叉搜索树。它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

  插入(删除)操作的时间复杂度为O(logn)。

0x01 树形结构及建树

  作为一棵树,线段树的结构体大概是这样的:

struct SegmentTree{        //树的结构体
long long l,r; //覆盖的区间的左右指针
long long value; //该节点上维护的值
long long tag; //lazy标记,下面会写到,这也是线段树的精髓
}node[MAX_N*+];

  要想建一棵线段树,可以利用线段树是个二叉树的性质,进行递归建树:

void build(REG long long p,REG long long l,REG long long r){    //递归建树
node[p].l=l,node[p].r=r;
if (l==r){ //访问到了最底部,不可再分
node[p].value=read();
return ;
}
long long mid=(l+r)>>;
build(p<<,l,mid);
build((p<<)+,mid+,r);
node[p].value=node[p<<].value+node[(p<<)+].value;
}

  

  这样我们就拥有了一棵树。

0x02 lazy标记(懒标记)

  上面提到过,线段树的精髓是lazy标记。不管是要查询还是更改数列,都绕不开它。

  如果要修改一段区间(比如同时都增加k),最容易想到的方法是暴力枚举,一个个修改。这样操作m次,最坏时间复杂度是O(mn)。显然是过不去的。

  所以我们可以想:如果不修改那么多节点呢?很容易想到,我们只关心查询需要用到的节点——并不是每一个被修改的节点都会在查询中被访问到的。打个比方:假设只有一次修改,一次询问。修改修改[1,16],而询问只关心[1,8],那么我们对于[9,16]的修改都是没有意义的。

  可以想到,如果点i的两个儿子都要同时增加k,考虑先不修改两个儿子,而给点i打一个标记,标记一下i的两个儿子都要被修改。

  如果在下面的询问里要访问到i的两个儿子之一,我们再对i的两个儿子进行修改(或下传标记),再把标记归零。如果两个儿子都会被访问到,就直接取i的值(i点维护的value值是两个儿子节点的和)。

  这差不多就是lazy标记的思想,时间复杂度就降到了O(mlogn)。

  写一个下传标记的函数:

void push_down(REG const long long& p){
node[p<<].value+=(node[p].tag*(node[p<<].r-node[p<<].l+));
node[(p<<)+].value+=(node[p].tag*(node[(p<<)+].r-node[(p<<)+].l+));
node[p<<].tag+=node[p].tag;
node[(p<<)+].tag+=node[p].tag;
node[p].tag=;
}

  按照这样的思想,我们就可以写出线段树了。其他的在最终代码里写了,不再赘述。

0x03 AC代码

#include <cstdio>
#define REG register
#define MAX_N 100000 using namespace std; long long n,m;
long long a[MAX_N+]; long long ch,x,y,z; inline long long read(){ //快速读入
REG long long ch=getchar(),x=,f=;
while (ch<''||ch>''){
if (ch=='-') f=-;
ch=getchar();
} while (ch<=''&&ch>=''){
x=x*+ch-'';
ch=getchar();
}return x*f;
} struct SegmentTree{ //树的结构体
long long l,r;
long long value;
long long tag;
}node[MAX_N*+]; void build(REG long long p,REG long long l,REG long long r){ //递归建树
node[p].l=l,node[p].r=r;
if (l==r){ //访问到了最底部,不可再分
node[p].value=read();
return ;
}
long long mid=(l+r)>>;
build(p<<,l,mid);
build((p<<)+,mid+,r);
node[p].value=node[p<<].value+node[(p<<)+].value;
} void push_down(REG const long long& p){
node[p<<].value+=(node[p].tag*(node[p<<].r-node[p<<].l+));
node[(p<<)+].value+=(node[p].tag*(node[(p<<)+].r-node[(p<<)+].l+));
node[p<<].tag+=node[p].tag;
node[(p<<)+].tag+=node[p].tag;
node[p].tag=; //标记归零
} void change(REG long long p,REG const long long& x,REG const long long& y,REG long long& z){
if (x<=node[p].l&&y>=node[p].r){ //是否完全在区间内
node[p].value+=(z*(node[p].r-node[p].l+));
node[p].tag+=z;
return ;
}
if (node[p].tag) push_down(p); //不完全在区间内,如果有lazy标记,下传
REG long long mid=(node[p].l+node[p].r)>>;
if (x<=mid) change(p<<,x,y,z);
if (y>mid) change((p<<)+,x,y,z);
node[p].value=node[p<<].value+node[(p<<)+].value;
} inline long long ask(REG long long p,REG const long long& x,REG const long long& y){
if (x<=node[p].l&&y>=node[p].r) return node[p].value;
push_down(p);
REG long long mid=(node[p].l+node[p].r)>>;
long long ans=;
if (x<=mid) ans+=ask(p<<,x,y);
if (y>mid) ans+=ask((p<<)+,x,y);
return ans;
} int main(){
// freopen("test1.txt","r",stdin);
n=read(),m=read();
build(,,n);
REG long long ch;
while (m--){
ch=read();
if (ch==){
x=read(),y=read(),z=read();
change(,x,y,z);
}
else{
x=read(),y=read();
printf("%lld\n",ask(,x,y));
}
}
return ;
}

  如果写的有哪里不对,欢迎指出,轻喷orz

[参考百度百科及网友讲解]

线段树基本操作(Segment Tree)的更多相关文章

  1. 线段树(Segment Tree)总结

    0 写在前面 怎么说呢,其实从入坑线段树一来,经历过两个阶段,第一个阶段是初学阶段,那个时候看网上的一些教学博文和模板入门了线段树, 然后挑选了一个线段树模板作为自己的模板,经过了一点自己的修改,然后 ...

  2. 【数据结构】线段树(Segment Tree)

    假设我们现在拿到了一个非常大的数组,对于这个数组里面的数字要反复不断地做两个操作. 1.(query)随机在这个数组中选一个区间,求出这个区间所有数的和. 2.(update)不断地随机修改这个数组中 ...

  3. 第二十九篇 玩转数据结构——线段树(Segment Tree)

          1.. 线段树引入 线段树也称为区间树 为什么要使用线段树:对于某些问题,我们只关心区间(线段) 经典的线段树问题:区间染色,有一面长度为n的墙,每次选择一段墙进行染色(染色允许覆盖),问 ...

  4. 线段树:Segment Tree(单点修改/区间修改模板) C++

    线段树是非常有效的数据结构,可以快速的维护单点修改,区域修改,查询最大值,最小值等功能. 同时,它也很重要.如果有一天比赛,你卡在了一道线段树模板题目上,这就真的尴尬了.不过,随着时代的进步,题目也越 ...

  5. 线段树(segment tree)

    线段树是一种二叉搜索树,它的每一个结点对应着一个区间[L, R],叶子结点对应的区间就是一个单位区间,即L == R.对于一个非叶子结点[L, R],它的左儿子所表示的区间是[L, (L +R)/2] ...

  6. 【转】Senior Data Structure · 浅谈线段树(Segment Tree)

    本文章转自洛谷 原作者: _皎月半洒花 一.简介线段树 ps: _此处以询问区间和为例.实际上线段树可以处理很多符合结合律的操作.(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[ ...

  7. 线段树(segment tree )

    http://www.cnblogs.com/TenosDoIt/p/3453089.html 写的非常好! 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很 ...

  8. 数据结构--树状数组&&线段树--基本操作

    随笔目的:方便以后对树状数组(BIT)以及基本线段树的回顾 例题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166 例题:hdu 1166 敌兵布阵 T ...

  9. 李超线段树(segment[HEOI2013]-洛谷T4097)

    (neng了好久好久才糊弄懂得知识点...) 一.李超线段树 在线动态维护一个二维平面直角坐标系, 支持插入一条线段, 询问与直线x = x0相交的所有线段中,交点y的最大/小值 (若有多条线段符合条 ...

随机推荐

  1. 安装wampserver后,在www文件夹下面写php文件,而在网页里输入localhost而无法打开php文件时解决办法汇总

    wampserver安装后,在www文件夹下面写入xx.PHP文件,然后在网页里输入localhost:xx.PHP. 你可能会遇到如下三种情况: 情形一:网页上显示空白,按F12,出现404的错误. ...

  2. Thrift结构分析及增加取客户端IP功能实现

    目录 目录 1 1. 前言 1 2. 示例Service 1 3. 网络部分类图 2 4. 线程模式 3 4.1. IO线程 3 4.2. 工作线程 4 4.2.1. 工作线程类图 4 4.2.2.  ...

  3. java经典开发模式

    Java Web开发方案有多种可供选择,这里列举一些经典的开发模式进行横向比较,为Java Web的开发模式选择提供参考.除此之外还有好多方案(如Tapestry和Wicket等等)并不了解,这里就不 ...

  4. 1、Docker介绍

      Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化.容器完全使用沙箱机制,相互之间不会有任何接口 ...

  5. The Scope Chain

    JavaScript is a lexically scoped language: the scope of variable can be thought of as the set of sou ...

  6. [Erlang30]Erlang shell是如何工作的?

    一些关于Erlang启动进程的分析:希望你会喜欢. 原英文地址:http://ferd.ca/repl-a-bit-more-and-less-than-that.html 研究Erlang shel ...

  7. 命令式语言和声明式语言对比——JavaScript实现快速排序为例

    什么是命令式编程 (Imperative Programming)? 命令机器如何做事情,强调细节实现 java.c.c++等都属此类. “这些语言的特征在于,写出的代码除了表现出“什么(What)” ...

  8. [Java]java内存及数据区

    Java运行时的数据区包括:(其中前两个是线程共享的) 1.方法区(Method Area) 存储已被虚拟机加载的类信息.常量.静态变量.即时编译器编译后的代码等数据 2.堆(Heap) 存放对象实例 ...

  9. 基于python 3.5 所做的找出来一个字符串中最长不重复子串算法

    功能:找出来一个字符串中最长不重复子串 def find_longest_no_repeat_substr(one_str): #定义一个列表用于存储非重复字符子串 res_list=[] #获得字符 ...

  10. 知物由学 | AI网络安全实战:生成对抗网络

    本文由  网易云发布. “知物由学”是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道.“知物由学” ...