数据结构--线段树

一、定义

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点\([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. mysql 批量修改 表字段/表/数据库 字符集和排序规则

    今天接到一个任务是需要把数据库的字符编码全部修改一下,写了以下修正用的SQL,修正顺序是   表字段 > 表 > 数据库. 表字段修复: #改变字段数据 SELECT TABLE_SCHE ...

  2. jQuery的parent和parents和closest区别

    1.parent是指取得一个包含着所有匹配元素的唯一父元素的元素集合.2.parents则是取得一个包含着所有匹配元素的祖先元素的元素集合(不包含根元素).可以通过一个可选的表达式进行筛选.3.clo ...

  3. eros 修改 android上原生picker的颜色的呢

    修改选中颜色和文字颜色 修改文件如下 修改窗口底色

  4. It's the loneliest feeling not to know who you are.

    It's the loneliest feeling not to know who you are.最孤独的感觉莫过于不知道自己是谁.

  5. git版本分支和分支、分支和主分支切换

    问题描述: 公司里项目管理使用的是gitLab(收费的), 如果开发人员提交代码,  需要首先创建一个分支, 然后把代码提交到你创建的分支上去(不允许把代码直接提交到主分支上). 在代码提交到已经创建 ...

  6. 【C++】【MFC】定义全局变量的方法

    在stafx.h 里面加extern CString place在stafx.app 里面加CString place

  7. Windows Phone Emulator 模拟器常用快捷键

    在使用Windows Phone 的开发的时候,在目前大家还很难买到真实的Windows Phone 设备的情况下,我们用来调试自己的程序经常用到的可能就是Emulator了.经常会有人问我说,用鼠标 ...

  8. UVA 1213 - Sum of Different Primes(递推)

    类似一个背包问题的计数问题.(虽然我也不记得这叫什么背包了 一开始我想的状态定义是:f[n = 和为n][k 个素数]. 递推式呼之欲出: f[n][k] = sigma f[n-pi][k-1]. ...

  9. 【洛谷3275】[SCOI2011] 糖果(差分约束系统入门题)

    点此看题面 大致题意: 有\(N\)个小朋友,要求每个人都得到糖果,且每个人的糖果总数满足一定的关系式,请你求出至少共分给小朋友们多少糖果. 关系式的转换 首先,我们可以将题目中给定的式子进行转换: ...

  10. java基础编程——链表反转

    题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList.   题目代码 /** * @program: JavaCode * @description:输入一个链表,按链表值从尾到头 ...