线段树(知识篇)

您所在的位置:网站首页 线段树知乎 线段树(知识篇)

线段树(知识篇)

2024-07-10 00:02| 来源: 网络整理| 查看: 265

线段树(知识篇)

线段树,从入门到入坑 - xyz的小窝 - 洛谷博客 (luogu.com.cn)

算法学习笔记(14): 线段树 - 知乎 (zhihu.com)

如何快速求出一个序列的区间和呢?可以使用前缀和。如何快速求出一个序列的最值呢?可 以使用 ST 表。这两种数据结构在建立的时候颇费功夫,但使用的时候效率很高。如果再增加一 个需求:时不时的修改序列的值,那么这两种数据结构就无法高效完成了。不过可以使用线段树 来解决这类问题。

在基础篇中,我们已经学习了二叉树的有关概念。而线段树是一种特殊的二叉树,它可以将 一个线性的序列组织成一个树状的结构,从而可以在对数的时间复杂度下访问序列上的任意一个 区间并进行维护。在本章中,将学习线段树的构建方法和一些简单的应用。

引言

线段树,它将一段区间划分为若干 单位区间 ,每一个节点都储存着一个区间。它 功能强大 ,支持区间求和,区间最大值,区间修改,单点修改等操作。它的大致思想是:将一段大区间平均地划分成 2 22 个小区间,每一个小区间都再平均分成 2 22 个更小区间……以此类推,直到每一个区间的 L 等于 R (这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。可以用线段树维护的问题必须满足 区间加法 ,否则是不可能将大问题划分成子问题来解决的。

例如:

符合区间加法的例子:

数字之和——总数字之和 = 左区间数字之和 + 右区间数字之和

最大公因数(GCD)——总GCD = gcd( 左区间GCD , 右区间GCD );

最大值——总最大值=max(左区间最大值,右区间最大值)

不符合区间加法的例子:

众数——只知道左右区间的众数,没法求总区间的众数

01序列的最长连续零——只知道左右区间的最长连续零,没法知道总的最长连续零

辨析:区间[L,R]指的是下标从L到R的这(R-L+1)个数,而不是指一条连续的线段。

线段树是将每个区间[L,R]分解成[L,M]和[M+1,R] (其中M=(L+R)/2 这里的除法是整数除法,即对结果下取整)

由于树的天然结构,线段树天生可以通过递归来逐步分解。

img

上图中,每个区间都是一个节点,每个节点存自己对应的区间的统计信息。

线段树的点修改:

每层只有一个节点包含我们要修改的节点,因此每层只需要更新一个节点即可,复杂度是(logn)

动图区间修改

使用懒标记后,对于那些正好是线段树节点的区间,我们不继续递归下去,而是打上一个懒标记,将来要用到它的子区间的时候,再向下传递。

更新时,我们是从最大的区间开始,递归向下处理。

pushdown操作

它可以把懒标记下传。

设想一下,如果我们要把懒标记下传,应该注意什么呢?

首先,要给子节点打上懒标记。

然后,我们要修改子节点上的值。

最后,不要忘记把这个节点的懒标记清空。

详见代码

区间查询

有了区间修改的经验,区间查询的方法完全类似。

接下来看模板罢

【模板】线段树 1题目描述

如题,已知一个数列,你需要进行下面两种操作:

将某区间每一个数加上 $k$。 求出某区间每一个数的和。 输入格式

第一行包含两个整数 $n, m$,分别表示该数列数字的个数和操作的总个数。

第二行包含 $n$ 个用空格分隔的整数,其中第 $i$ 个数字表示数列第 $i$ 项的初始值。

接下来 $m$ 行每行包含 $3$ 或 $4$ 个整数,表示一个操作,具体如下:

1 x y k:将区间 $[x, y]$ 内每个数加上 $k$。 2 x y:输出区间 $[x, y]$ 内每个数的和。 输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1样例输入 #112345675 51 5 4 2 32 2 41 2 3 22 3 41 1 5 12 1 4 样例输出 #112311820 提示

对于 $30\%$ 的数据:$n \le 8$,$m \le 10$。对于 $70\%$ 的数据:$n \le {10}^3$,$m \le {10}^4$。对于 $100\%$ 的数据:$1 \le n, m \le {10}^5$。

保证任意时刻数列中所有元素的绝对值之和 $\le {10}^{18}$。

【样例解释】

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201#includeusing namespace std;using ll=long long;const ll N=1e6;#define rep(i,a,n) for(int i=a;i>1//如果p=p) //如果查询的位置在左子树 //就递归查询左子树{ return query1(u*2,l,mid,p);}else//反之查询右子树{ return query1(u*2+1,mid+1,r,p);}}//由于查询没有对区间和进行修改操作,因此不需要Pushup}//2.单点修改操作ll update1(int u,int l,int r,int p,ll x){ if(l==r) { w[u]=x; //到达叶子节点直接返回即可 } else { int mid=(l+r)>>1; if(mid>=p) { update1(u*2,l,mid,p,x); } else { update1(u*2+1,mid+1,r,p,x); } pushup(u); //更新后要修改当前节点的和 }}//对点的操作到这里就结束了//下面进入区间查询//区间查询//首先假设我们要查询的区间是[l,r]//如果当前节点代表的区间[L,R]被所询问的区间[l,r]所包含//l,r就是 查询的区间//那么直接返回当前区间的区间和//也即是说L>=r并且R>m;rep(i,1,n){ cin>>a[i];}build(1,1,n);rep(i,1,m){ int op,x,y; cin>>op; ll k; if(op==1) { cin>>x>>y>>k; update(1,1,n,x,y,k); } else { cin>>x>>y; coutq; for(int i=1;i>a[i]; build(1,1,n); while(q--){ int l,r,k,c; cin>>c>>l>>r; if(c==1){ cin>>k; update(1,l,r,k); } else couta[i]; build(1,1,n);//建树 while(q--){ int t,b,c; cin>>t>>b>>c; if(t==1) change(1,b,c); else cout>p; for(int i=1;i>a[i]; build(1,1,n); while(m--){ cin>>ch>>t>>g; if(ch==1){ cin>>c; change(1,t,g,0,c); } else if(ch==2){ cin>>c; change(1,t,g,c,1); } else cout>m; coutp; insert(1,1,n,m,p); } else{ //下车 cin>>m>>p; insert(1,1,n,m,-p); } }} 查询第k大数123456int kth(int x,int l,int r,int k){ if(l==r) return l;//查到了,返回即可 int mid=(l+r)/2; if(k>x; switch(opt){ case 1: insert(root,-N,N,x,1);//因为动态开点的插入写成引用形式,所以需要带进去一个变量 break; case 2: insert(root,-N,N,x,-1);//删除 break; case 3: cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3