数据结构--线段树

一、定义

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点\([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. Java并发(三):实例引出并发应用场景

    前两篇介绍了一些Java并发的基础知识,博主正巧遇到一种需求:查询数据库,根据查询结果集修改数据库记录,但整个流程是做成了一个schedule的,并且查询比较耗时,并且需要每两分钟执行一次,cpu经常 ...

  2. 读取java配置文件properties

    java项目里很多参数都是写在配置文件properties上,如果需要读取的话,可以使用jdk里提供的Properties类进行处理. 具体写法如下: public class PropertiesC ...

  3. hibernate课程 初探单表映射3-1 单一主键

    本节简介: 1 单一主键的两种赋值方式:手动赋值(assigned)和自动赋值(native) 2 mysql和oracle赋值的不同形式 3 demo 2 native由底层数据库生成标识符,如果是 ...

  4. html 02-认识html

    1. HTML 初识 HTML 指的是超文本标记语言 (Hyper Text Markup Language)是用来描述网页的一种语言. HTML 不是一种编程语言,而是一种标记语言 (markup ...

  5. mui的上拉下载和下拉刷新

    head部分(引入mui) <link href="./resources/css/mui.min.css" rel="stylesheet" /> ...

  6. keil 系列问题

      1.为了让keil支持stm32f0系列,安装了keil4更高级的版本,但是发现编译时弹出异常提示框,经过一番折腾后找到解决办法,首先把电脑登陆账户名改为非中文的,然后卸载了keil重装就可以了. ...

  7. FRM-92050错误

    使用IE8在打开EBS Form界面时,窗口提示信息“Internet Explorer 已对此页面进行了修改,以帮助阻止跨站脚本.单击此处,获取详细信息...”或者R12 IE8中出"FR ...

  8. centos升级3.10内核到4.4

    为了满足k8s对内核要求,默认Centos 7的内核为3.10建议升级到4.x内核 默认3.10内核升级为4.x内核 rpm -Uvh http://www.elrepo.org/elrepo-rel ...

  9. <已解决> Eclipse启动失败

    参考:http://stackoverflow.com/questions/15404964/starting-eclipse-results-in-failed-to-create-java-vir ...

  10. UVA Live Achrive 4327 Parade (单调队列,dp)

    容易想到dp[i][j]表示在第i行j个路口的开始走最大高兴值. 每次可以向左走,或者向右边走,然后向北走.(或者直接往北) 向左走到,状态转移为dp[i][j] = dp[i][k] + happy ...