博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线段树模板讲解
阅读量:5267 次
发布时间:2019-06-14

本文共 4542 字,大约阅读时间需要 15 分钟。

洛谷题目链接:

线段树是一种用于区间修改查询的数据结构,可以支持的操作有单点修改区间查询,区间修改单点查询,区间修改区间查询等.

线段树有递归版和结构体版,递归版在处理一开始没有赋初始值的问题时可以不用建树,而结构体版的则显得比较条理清晰.

线段树比树状数组的代码复杂的多,但是树状数组多支持一个区间修改区间查询的操作,并且可以与其他的一些数据结构相适应(像是树链剖分等),也有很多的在此基础上的提高的算法,线面开始讲解一下思路.

 

线段树操作如下:

  1. 输入点权,建树.
  2. 进行修改和查询.

 

建树:

在线段树中建树一般采用的是递归的方式建树,在建树的过程中保留每个点的信息(区间左端点,右端点,区间和等),在线段树中的每个节点就是一个区间.

 

代码如下:

void build(lol root,lol left,lol right){
//节点,节点左端点,节点右端点 if(left==right){
//当节点左端点等于右端点时,即为叶子节点 sum[root]=w[left];//叶子节点的区间和即为点权 return;//这里记得要退出回溯 } build(root*2,left,mid);//左子树 build(root*2+1,mid+1,right);//右子树递归建树 sum[root]=sum[ll(root)]+sum[rr(root)];//回溯时收集区间和}

那么这样建好的树就会有这样的性质:

  1. 当前节点的左右儿子分别是root*2和root*2+1
  2. 当前节点包括的范围刚好是左右儿子的区间的并集

 所以之后在进行修改,查询等操作时会很方便.

 

下移懒惰标记:

这里提及了一个重要的操作:lazy[]数组,懒惰标记.

lazy数组用于保存修改在某个区间上的值,但不即时修改赋值到节点上,而是在之后查询时需要查询它子树的值的时候再将懒惰标记下移.

那么下移lazy标记就是将当前区间的修改细化到每个小区间上.具体细节见代码注释:

void pushdown(int root,int left,int right){  lazy[root*2]+=lazy[root];//左右子树加上上面节点的标记  lazy[root*2+1]+=lazy[root];  sum[root*2]+=lazy[root]*(mid-left+1);//将区间和加上子树的个数乘标记的大小  sum[root*2+1]+=lazy[root]*(right-mid);  lazy[root]=0;//消去节点的懒惰标记}

为什么修改区间和时要将左子树乘上(mid-left+1)呢?因为左子树的左端点是left,右端点是mid,那么正好(mid-left+1)就是左子树的节点数,乘上lazy[root]的值也就是将它的子树每个加上lazy[root]的值.

 

修改:

修改时是寻找一个能全部包含于修改范围的区间,并将它打上lazy标记.

一个需要修改的范围可以看做是一个个小的范围的集合,如:

1到8的区间可以看做是[1,5]和[6,8];5到6的区间可以看做是[5,5]和[6,6];

如果是将1~8加5,则将区间[1,5]和区间[6,8]节点的懒惰标记加上5就可以了.

代码如下:

1 void updata(int root,int left,int right,int l,int r,int val){ 2   if(l<=left&&right<=r){
//如果找到一个能全部被包含于修改范围的区间 3 lazy[root]+=val;//则加上懒惰标记 4 sum[root]+=val*(right-left+1);//同时也要修改区间和,与pushdown的修改同理 5 return; 6 } 7 if(lazy[root]) pushdown(root,left,right);//修改时也要将之前的懒惰标记下移 8 if(l<=mid) updata(ll(root),left,mid,l,r,val); 9 if(mid

 

查询:

查询和修改操作比较像,也是将一个大区间细化为一个个小区间,找能被完全包含的区间进行查询.

下面直接上代码:

 

1 lol query(int root,int left,int right,int l,int r){2   if(l<=left&&right<=r) return sum[root];//完全被包含的区间直接返回区间和3   if(r

 

为什么要把懒惰标记下移的操作放在判断后呢?我们举个例子,假设已经递归到了叶子节点,如果先下移标记,就有可能导致数组的越界(向下移标记时左右子树的节点标号都是节点的两倍),所以要先进行判断区间的操作.

 

几个简单的操作讲完了,下面放一个完整模板:

1 #include
2 #define mid (left+right>>1) 3 #define ll(x) (x<<1) 4 #define rr(x) (x<<1|1) 5 using namespace std; 6 typedef long long lol; 7 const int N=500000; 8 9 lol n,m;10 lol w[N+10];11 lol sum[N+10];12 lol lazy[N+10];13 14 int gi(){15 int ans=0,f=1;char i=getchar();16 while(i<'0'||i>'9'){
if(i=='-')f=-1;i=getchar();}17 while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}18 return ans*f;19 }20 21 void build(lol root,lol left,lol right){22 if(left==right){23 sum[root]=w[left];24 return;25 }26 build(ll(root),left,mid);27 build(rr(root),mid+1,right);28 sum[root]=sum[ll(root)]+sum[rr(root)];29 }30 31 void pushdown(int root,int left,int right){32 lazy[ll(root)]+=lazy[root];33 lazy[rr(root)]+=lazy[root];34 sum[ll(root)]+=lazy[root]*(mid-left+1);35 sum[rr(root)]+=lazy[root]*(right-mid);36 lazy[root]=0;37 }38 39 void updata(int root,int left,int right,int l,int r,int val){40 if(l<=left&&right<=r){41 lazy[root]+=val;42 sum[root]+=val*(right-left+1);43 return;44 }45 if(lazy[root]) pushdown(root,left,right);46 if(l<=mid) updata(ll(root),left,mid,l,r,val);47 if(mid

 另外再贴一个结构体版的:

#include
#define ll(x) (x<<1)#define rr(x) (x<<1|1)using namespace std;const int N=400000+5;typedef long long lol;lol n, m;lol w[N];struct seg_tree{ lol sum, l, r, lazy;}t[N];lol gi(){ lol ans = 0 , f = 1; char i=getchar(); while(i<'0'||i>'9'){
if(i=='-')f=-1;i=getchar();} while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();} return ans * f;}void up(lol root){ t[root].sum = t[ll(root)].sum + t[rr(root)].sum;}void build(lol root,lol l,lol r){ int mid = l+r>>1; t[root].l = l , t[root].r = r; if(l == r){ t[root].sum = w[l]; return; } build(ll(root),l,mid); build(rr(root),mid+1,r); up(root);}void pushdown(lol root){ lol mid = t[root].l + t[root].r >> 1; t[ll(root)].lazy += t[root].lazy; t[rr(root)].lazy += t[root].lazy; t[ll(root)].sum += t[root].lazy*(mid-t[root].l+1); t[rr(root)].sum += t[root].lazy*(t[root].r-mid); t[root].lazy = 0;}void updata(lol root,lol l,lol r,lol val){ lol mid = t[root].l+t[root].r>>1; if(l<=t[root].l && t[root].r<=r){ t[root].sum += val * (t[root].r-t[root].l+1); t[root].lazy += val; return; } if(t[root].lazy) pushdown(root); if(l <= mid) updata(ll(root),l,r,val); if(mid < r) updata(rr(root),l,r,val); up(root);}lol query(lol root,lol l,lol r){ if(l<=t[root].l && t[root].r<=r) return t[root].sum; if(r

 

转载于:https://www.cnblogs.com/BCOI/p/8149181.html

你可能感兴趣的文章
CF461B Appleman and Tree
查看>>
CF219D Choosing Capital for Treeland
查看>>
杂七杂八的小笔记本
查看>>
51Nod1353 树
查看>>
CF1215E Marbles
查看>>
BZOJ2339 HNOI2011卡农(动态规划+组合数学)
查看>>
octave基本操作
查看>>
axure学习点
查看>>
WPF文本框只允许输入数字[转]
查看>>
dom4j 通用解析器,解析成List<Map<String,Object>>
查看>>
第一个项目--用bootstrap实现美工设计的首页
查看>>
使用XML传递数据
查看>>
TYVJ.1864.[Poetize I]守卫者的挑战(概率DP)
查看>>
0925 韩顺平java视频
查看>>
iOS-程序启动原理和UIApplication
查看>>
SpringMVC入门(二)—— 参数的传递、Controller方法返回值、json数据交互、异常处理、图片上传、拦截器...
查看>>
git的安装
查看>>
mysql 8.0 zip包安装
查看>>
Spring框架系列(三)--Bean的作用域和生命周期
查看>>
springboot + mybatis
查看>>