数据结构--线段树

一、定义

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点\([a,b]\),它的左儿子表示的区间为\([a,(a+b)/2]\),右儿子表示的区间为\([(a+b)/2+1,b]\)。因此线段树是平衡二叉树,最后的叶子节点数目为\(N\),即整个线段区间的长度。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为\(O(logN)\)。而未优化的空间复杂度为\(4N\)

你们可能会问什么是区间树,我也不知道。



\(如上图就是一颗[1,8]的线段树。\)

二、性质

\(每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]。\)

\(对于节点k,左孩子为k * 2,右孩子为k * 2 + 1,和二叉树一样\)

三、基本操作

\(线段树的基础操作主要有5个:建树、单点查询、单点修改、区间查询、区间修改。\)

0.结构体

可以使用结构体也可以使用数组,看个人喜好。

struct node{
int l,r,w;//l,r分别表示区间左右端点,w表示区间和
}tree[MAXN*4+1];//注意线段树要开四倍空间。

1.建树

void build(int l,int r,int now){
tree[now].l=l,tree[now].r=r;//记下now这个节点所表示的区间。
if(l==r){//now节点为叶子结点。
scanf("%d",tree[now].w);//读入叶子结点的值。
return;//不用进行下面的了。
}
int mid=(l+r)>>1;//>>1相当于/2
build(l,mid,now<<1);//<<1相当于*2
build(mid+1,r,now<<1|1);//<<1|1相当于*2+1
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//更新区间和
}

2.单点查询

询问第\(x\)个点的值。

void ask_single(int x,int now){
if(tree[now].l==tree[now].r){//叶子结点,即最终答案。
ans=tree[now].w;
return;
}
int mid=(tree[now].l+tree[now].r)>>1;//计算区间的中点。
if(x<=mid){
ask_single(x,now<<1);//查找该点的左孩子
}else{
ask_single(x,now<<1|1);//查找该点的有孩子
}
}

3.单点修改

给第\(x\)个点加上\(y\)。(和单点查询差不多qwq)

void updata_single(int x,int y,int now){
if(tree[now].l==tree[now].r){
tree[now].w+=y;
return;
}
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid){
updata_single(x,y,now<<1);
}else{
updata_single(x,y,now<<1|1);
}
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//单点修改后要更新区间和
}



修改的过程像上图一样递归修改,当修改完单点后再去更新上面的区间和。

4.区间修改

建议先看下面的区间查询。

将\([x,y]\)区间每一个数加上\(x\)。

区间里都是点,进行\(y-x+1\)次单点修改。(太慢了,应该不会有人用吧。)

void update_range(int x,int y,int c){
for(int i=x;i<=y;++i){
update_single(i,c,1);
}
}

上面这个做法数据不水的话就\(TLE\)了(在线段树1中只能拿\(70\)分)。

将某区间每一个数加上\(x\),这个是可以加优化的。

修改需要辣么多时间,不修改不就可以了吗?

差不多,是在没用到的时候不修改。

就像是过年各种亲戚给你压岁钱,然后都到了你家长手里,\(\color{red}{你用的时候再给你}\)。(然后这钱可能永远也到不了你手里了,qwq)。

实现方法:

1.用一个标记记录下这个增量。

2.当要修改的区间完全包含当前区间就给当前区间的标记加上这个增量,不再向下递归。

当需要查询子节点时怎么办?

用到一个下放操作。

1.当前节点的标记累加到子节点的标记中。

2.修改子节点状态。

3.该节点标记清\(0\)。

结构体:

struct node{
int w,l,r,lazy;//lazy就是这个标记。
}tree[MAXN*4+1];

下放操作:

inline void pushdown(int now){
if(tree[now].lazy){
tree[now<<1].lazy+=tree[now].lazy;
tree[now<<1|1].lazy+=tree[now].lazy;//将lazy标记向下传.
tree[now<<1].w+=(tree[now<<1].r-tree[now<<1].l+1)*tree[now].lazy;
tree[now<<1|1].w+=(tree[now<<1|1].r-tree[now<<1|1].l+1)*tree[now].lazy;//更新区间和
tree[now].lazy=0;//清零
}
}

区间修改:

void update_range(int x,int y,int c,int now){
if(tree[now].l>=x&&tree[now].r<=y){//当前区间包含于要修改的区间.
tree[now].w+=(tree[now].r-tree[now].l+1)*c;
tree[now].lazy+=c;
return;
}
if(tree[now].lazy) pushdown(now);//下传
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) update_range(x,y,c,now<<1);
if(y>mid) update_range(x,y,c,now<<1|1);
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//这几行和区间查询差不多
}

因为加入了lazy标记所以其他的操作也有改变。

单点查询:

int ask_single(int x,int now){
if(tree[now].l==tree[now].r){
return tree[now].w;
}
if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) ask_single(x,now<<1);
else ask_single(x,now<<1|1);
}

区间查询:

void ask_range(int x,int y,int now){
if(tree[now].l>=x&&tree[now].r<=y){
ans+=tree[now].w;
return;
}
if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) ask_range(x,y,now<<1);
if(y>mid) ask_range(x,y,now<<1|1);
}

单点修改

void update_single(int x,int c,int now){//
if(tree[now].l==tree[now].r){
tree[now].w+=c;
return;
}
if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) update_single(x,c,now<<1);
else update_single(x,c,now<<1|1);
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}

5.区间查询

求出\([x,y]\)区间每一个数的和。

在查询的过程中存在以下几种情况:



void ask_range(int x,int y,int now){
if(tree[now].l>=x&&tree[now].r<=y){//[l,r]是[x,y]的子集
ans+=tree[now].w;
return;//不结束的话进行下面的会重复计算。
}
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid){//要查询的区间在当前区间的左边
ask_range(x,y,now<<1);
}
if(y>mid){//要查询的区间在当前区间的右边
ask_range(x,y,now<<1|1);
}
}

6.总

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define MAXN 500001
using namespace std;
struct node{
int l,r,w;
int lazy;
}tree[MAXN<<2];
int ans;
inline int read(){
int x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=!f;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return f?-x:x;
}
void build(int l,int r,int now){
tree[now].l=l,tree[now].r=r;
tree[now].lazy=0;
if(tree[now].l==tree[now].r){
tree[now].w=read();
return;
}
int mid=(tree[now].l+tree[now].r)>>1;
build(l,mid,now<<1),build(mid+1,r,now<<1|1);
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
inline void pushdown(int now){
tree[now<<1].lazy+=tree[now].lazy;
tree[now<<1|1].lazy+=tree[now].lazy;
tree[now<<1].w+=tree[now].lazy*(tree[now<<1].r-tree[now<<1].l+1);
tree[now<<1|1].w+=tree[now].lazy*(tree[now<<1|1].r-tree[now<<1|1].l+1);
tree[now].lazy=0;
}
void update_single(int x,int k,int now){//单点修改
if(tree[now].l==tree[now].r){
//tree[now].w=k;将x这个位置的数改为k
//tree[now].w+=k;将x这个位置的数加上k
return;
}
if(tree[now].lazy!=0) pushdown(now);
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) update_single(x,k,now<<1);
else update_single(x,k,now<<1|1);
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
int ask_single(int x,int now){//单点查询
if(tree[now].l==tree[now].r) return tree[now].w;
if(tree[now].lazy!=0) pushdown(now);
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) return ask_single(x,now<<1);
else return ask_single(x,now<<1|1);
}
void update_range(int x,int y,int k,int now){//区间修改
if(tree[now].l>=x&&tree[now].r<=y){
tree[now].w+=(tree[now].r-tree[now].l+1)*k;
tree[now].lazy+=k;
return;
}
if(tree[now].lazy!=0) pushdown(now);
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) update_range(x,y,k,now<<1);
if(y>mid) update_range(x,y,k,now<<1|1);
tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
void ask_range(int x,int y,int now){
if(tree[now].l>=x&&tree[now].r<=y){
ans+=tree[now].w;
return;
}
if(tree[now].lazy!=0) pushdown(now);
int mid=(tree[now].l+tree[now].r)>>1;
if(x<=mid) ask_range(x,y,now<<1);
if(y>mid) ask_range(x,y,now<<1|1);
} int main(){
return 0;
}

四、题目

单点修改、区间查询模板

Codevs 1080

洛谷P3374

区间修改、区间查询模板

洛谷P3372

Codevs1082

区间修改、单点查询

洛谷P3368

Codevs1081

五、鸣谢

学姐的Blog

百度百科

线段树[To be continued]的更多相关文章

  1. CodeForces 356A_(set应用,线段树)

    A. Knight Tournament time limit per test 3 seconds memory limit per test 256 megabytes input standar ...

  2. bzoj3932--可持久化线段树

    题目大意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第 ...

  3. codevs 1082 线段树练习 3(区间维护)

    codevs 1082 线段树练习 3  时间限制: 3 s  空间限制: 128000 KB  题目等级 : 大师 Master 题目描述 Description 给你N个数,有两种操作: 1:给区 ...

  4. codevs 1576 最长上升子序列的线段树优化

    题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...

  5. codevs 1080 线段树点修改

    先来介绍一下线段树. 线段树是一个把线段,或者说一个区间储存在二叉树中.如图所示的就是一棵线段树,它维护一个区间的和. 蓝色数字的是线段树的节点在数组中的位置,它表示的区间已经在图上标出,它的值就是这 ...

  6. codevs 1082 线段树区间求和

    codevs 1082 线段树练习3 链接:http://codevs.cn/problem/1082/ sumv是维护求和的线段树,addv是标记这歌节点所在区间还需要加上的值. 我的线段树写法在运 ...

  7. PYOJ 44. 【HNSDFZ2016 #6】可持久化线段树

    #44. [HNSDFZ2016 #6]可持久化线段树 统计 描述 提交 自定义测试 题目描述 现有一序列 AA.您需要写一棵可持久化线段树,以实现如下操作: A v p x:对于版本v的序列,给 A ...

  8. CF719E(线段树+矩阵快速幂)

    题意:给你一个数列a,a[i]表示斐波那契数列的下标为a[i],求区间对应斐波那契数列数字的和,还要求能够维护对区间内所有下标加d的操作 分析:线段树 线段树的每个节点表示(f[i],f[i-1])这 ...

  9. 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序

    3779: 重组病毒 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 224  Solved: 95[Submit][Status][Discuss] ...

随机推荐

  1. vue 导出excel

    1.安装三个依赖包 npm install -S file-saver npm install -S xlsx npm install -D script-loader 2.在项目中创建一个文件夹(比 ...

  2. 树莓派-(一)开箱到点亮一些坑(无屏、无wlan、无直连键鼠)

    0x00.前期准备: 材料: 树莓派3b+ 板子 * 1,适配电源 * 1,网线 * 2,sd卡16G * 1,读卡器 * 1 安装时注意,3b+三个散热片贴好.小风扇接线要接对 工具: 0x01. ...

  3. WIn10 电脑运行Docker

    参考地址: https://www.cnblogs.com/linjj/p/5606687.html https://docs.docker.com/engine/reference/commandl ...

  4. intelij idea相关笔记--持续更新

    一.快捷键: Ctrl+F 文件内查找 Ctrl+Shift+F 全局查找 Ctrl+Shift+N 查找文件 Ctrl+Alt+← 返回上一步 Ctrl+Alt+→ 返回下一步 二.编译相关: 如果 ...

  5. 红象云腾CRH 一键部署大数据平台

    平台: arm 类型: ARM 模板 软件包: azkaban hadoop 2.6 hbase hive kafka spark zeppelin azkaban basic software bi ...

  6. DB错误代码大全

    db2错误代码大全  sqlcode sqlstate 说明000 00000 SQL语句成功完成01xxx SQL语句成功完成,但是有警告+012 01545 未限定的列名被解释为一个有相互关系的引 ...

  7. 【洛谷2522】[HAOI2011] Problem b(莫比乌斯反演)

    点此看题面 大致题意: 求\(\sum_{x=a}^b\sum_{y=c}^d[gcd(x,y)==k]\). 关于另一道题目 在看这篇博客之前,如果你做过一道叫做[BZOJ1101][POI2007 ...

  8. vuejs组件的重要选项

    new Vue({ el:'#demo', data:{ message:'Hello vue.js!' } }) 我们看到这个括号里面包含了很多中间的选项,小括号里面其实是一些参数,这些参数指定了实 ...

  9. fdisk - Linux分区表操作工具软件

    总览 fdisk [-u]设备名 fdisk -l [-u] [设备名 ...] fdisk -s分区 ... fdisk -v 描述 硬盘可以被分成一个或多个逻辑磁盘,称为 分区. 这些分区信息都存 ...

  10. 说说qwerty、dvorak、colemak三种键盘布局

    [qwerty布局] qwerty布局大家应该都很熟悉了,全世界最普及的键盘布局. 截止到去年接触并使用dvorak布局之前,我使用了十几年qwerty布局,在http://speedtest.10f ...