线段树(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. [Jmeter]Jmeter环境搭建

    Jmeter环境搭建 1.  拷贝 \\szpc1450\apache-jmeter-2.7 整个目录到本机(我是放在D盘,以下路径说明以D盘为例) 2.  拷贝\\szpc1450\Tools\au ...

  2. Java 设计模式系列(五)原型模式

    Java 设计模式系列(五)原型模式 原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象.这就是选型模式的用意. 一.原型模 ...

  3. DataStage 错误集(持续更新)

    DataStage 错误集(持续更新) DataStage序列文章 DataStage 一.安装 DataStage 二.InfoSphere Information Server进程的启动和停止 D ...

  4. MyEclipse不能自动编译解决办法总结

    yEclipse在debug模式下,有时会碰到修改的文件无法自动编译的问题,以下的方法可以逐一尝试一下. 1.确保:Project->build automatically 已经被选上. 2.p ...

  5. linux源码安装php7.2.0

    1. 源码包下载地址 2. 解压php压缩包 tar –zxvf php-7.2.0.tar.gz 3. 进入解压后的 cd php7.2.0 4.安装php需要的扩展 yum install lib ...

  6. dojo表格操作的简单示例(建立表格)

    代码示例: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w ...

  7. SCI EI期刊

    coming soon 关键字:Computer Vision, Computing, Image, Intelligence, IEEE, Compution <Journal of Expe ...

  8. [label][JavaScript扩展] JavaSCript扩展

    http://www.idangero.us/sliders/swiper/ ,swipper for mobile terminal.

  9. An error "Host key verification failed" when you connect to other computer by OSX SSH

    Here's quick way to remove all entries in the host file: In an OSX terminal, type rm -f ~/.ssh/known ...

  10. Android-自定义控件之事件分发

    最大范围 外层蓝色区域是继承ViewGroup 中间红色区域,也是继承ViewGroup 最中间黑色区域,是继承View 布局相关的代码: <!-- 事件分发 --> <view.c ...