C语言-线段树
线段树讲解(数据结构、C++)

线段树讲解(数据结构、C++)声明:仅⼀张图⽚转载于,⾃⼰画太⿇烦了。
那个博客的讲解也很好,只是他⽤了指针的⽅式来定义线段树,⽽我⽤了结构体,并且他讲了线段树的更⾼级的操作,若对线段树的初级操作不理解,请继续阅读线段树作为⼀种⼗分常⽤的数据结构,在NOIP、NOI中⼴泛的出现,所以在这⾥对线段树进⾏简单的讲解。
线段树⽀持对⼀个数列的求和、单点修改、求最值(最⼤、最⼩)、区间修改(需要lazy标记,暂不讲解)。
这⼏种操作,时间复杂度是(logn)级别的,是⼀种⼗分优秀的数据结构。
因此其获得了⼴泛的应⽤。
定义:顾名思义,它是⼀种树形结构,但每段不是平常所学的⼀个点⼀个点的树,⽽是⼀条⼀条的线段,每条线段包含着⼀些值,其中最主要的是起始和结束点记作 l,r 即左端点和右端点。
那么该如何划分线段树呢?我们采⽤⼆分的思想,即每次将⼀段取半,再进⾏接下来的操作,这样综合了操作的⽅便程度和时间复杂度。
因为线段树通过⼆分得来,所以线段树是⼀颗⼆叉树。
这也⽅便了对⼉⼦查找。
下⾯是线段树的图,有利于理解:建树:仅仅知道模型还是不够的,建树的过程是线段树的关键(build(1,1,n))从⼀号开始,左端是1,右端是n位运算 i<<1 等效于 i/2 (i<<1)|1 等效于 i/2+1 加速。
inline void update(int i)更新i节点维护的值(求和,最⼤……){node[i].sum=node[i<<1].sum+node[(i<<1)|1].sum;node[i].maxx=max(node[i<<1].maxx,node[(i<<1)|1].maxx);}inline void build(int i,int l,int r)//inline 还是加速{node[i].l=l;node[i].r=r;//左右端点为当前递归到的 l 和 rif(l==r){//若l==r 则当前的树节点是真正意义上的点node[i].maxx=a[l];//最⼤值就是本⾝的值node[i].sum=a[l];//区间的和就是本⾝的值return;}int mid=(l+r)/2;//因为是⼆叉树所以以中点为分割点build(i<<1,l,mid);//根据⼆叉树的知识,左⼉⼦是i/2右⼉⼦是i/2+1build((i<<1)|1,mid+1,r);update(i);}数列求和:这是线段树的⼀个典型算法,其他的很多应⽤都是从中转化的。
线段树

线段树转载请注明出处,谢谢!/metalseed/article/details/8039326持续更新中···一:线段树基本概念1:概述线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍2:基本操作(demo用的是查询区间最小值)线段树的主要操作有:(1):线段树的构造void build(int node, int begin, int end);主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值1.#include <iostream>ing namespace std;3.4.const int maxind = 256;5.int segTree[maxind * 4 + 10];6.int array[maxind];7./* 构造函数,得到线段树 */8.void build(int node, int begin, int end)9.{10. if (begin == end)11. segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */12. else13. {14. /* 递归构造左右子树 */15. build(2*node, begin, (begin+end)/2);16. build(2*node+1, (begin+end)/2+1, end);17.18. /* 回溯时得到当前node节点的线段信息 */19. if (segTree[2 * node] <= segTree[2 * node + 1])20. segTree[node] = segTree[2 * node];21. else22. segTree[node] = segTree[2 * node + 1];23. }24.}25.26.int main()27.{28. array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3;29. build(1, 0, 5);30. for(int i = 1; i<=20; ++i)31. cout<< "seg"<< i << "=" <<segTree[i] <<endl;32. return 0;33.}此build构造成的树如图:(2):区间查询int query(int node, int begin, int end, int left, int right);(其中node为当前查询节点,begin,end为当前节点存储的区间,left,right为此次query所要查询的区间)主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息比如前面一个图中所示的树,如果询问区间是[0,2],或者询问的区间是[3,3],不难直接找到对应的节点回答这一问题。
线段树

对于线段树中的每个结点, 其表示一个区间,我们可以记录和这个区间相关 的一些信息(如最大值、最小值、和等) ,但要满足可二分性,即能直接由其子
结点的相关信息得到。 对于询问区间信息和修改区间信息的操作,线段树一般在能 O(log n) 的时间 内完成,而且常数相对较小(和后面的伸展树比较) ,算是一种高效实用的数据 结构。 (2)线段树的基本操作——查询区间信息 使用线段树的目的就是能高效地查找和修改区间信息, 下面先介绍第一个操 作——查询操作。 对于当前要查询的区间 a, b ,我们从线段树的根结点开始,如果当前结点 表示的区间被查询区间完全包含,那么更新结果,否则分别考察左右子结点,如 果查询区间与某个子结点有交集(也可能两个都有) ,那么就递归考察这个子结 点。代码框架如下1:
对于任意一个区间,会被划分成很多在线段树上存在的区间,可以证明,划 分出来的区间在线段树的每层最多有两个,又因为线段树的深度为 O(log n) ,因 此查询操作的时间复杂度为 O(log n) 。
(3)线段树的基本操作——修改区间信息 相对于查询区间信息,修改区间信息显得稍微复杂一些。
1
本文中的代码均使用 C++语言描述
(4)线段树特点总结 利用线段树, 我们可以高效地询问和修改一个数列中某个区间的信息,并且 代码也不算特别复杂。 但是线段树也是有一定的局限性的, 其中最明显的就是数列中数的个数必须 固定,即不能添加或删除数列中的数。对于这个问题,下面介绍的伸展树就可以 完美的解决。
(7,10]
(0,1] (1,2] (2,3] (3,5] (5,6]
(6,7] (7,8]
(8,10]
(3,4]
线段树五种基本操作代码模板

线段树五种基本操作代码模板这个模板是从⼤神:的博客上摘抄过来的注意:⼀开始要定义全局变量,n是节点数,m是操作数,查询操作都是void型的,所以需要有ans来承载答案,每个操作初始都要把ans置0,a,b是左右区间,p,a,b是输⼊模板⾥的区间修改和单点修改都是值加上⼀个数,如果是变成⼀个数把函数⾥的+=改成=就好另外找到⼀个模板好像⽐较好,先码住吧。
题⽬链接:#include<bits/stdc++.h>using namespace std;string s;int dat[4000005],lazy[4000005],a[4000005];int n;void pushup(int rt){dat[rt]=dat[2*rt]+dat[2*rt+1];};void build(int l,int r,int rt){if (l==r-1){dat[rt]=a[l];return;}int mid=(l+r)>>1;build(l,mid,rt<<1);build(mid,r,rt<<1|1);pushup(rt);}void pushdown(int rt,int l,int r){if (lazy[rt]!=-1){lazy[2*rt]=lazy[2*rt+1]=lazy[rt];dat[2*rt+1]=dat[2*rt]=lazy[rt]*(r-l)/2;lazy[rt]=-1;}}void update(int L,int R,int l,int r,int rt,int c){if (L<=l&&r<=R){dat[rt]=c*(r-l);lazy[rt]=c;return;}int mid=(l+r)>>1;pushdown(rt,l,r);if (L<mid) update(L,R,l,mid,rt<<1,c);if (R>mid) update(L,R,mid,r,rt<<1|1,c);pushup(rt);}//更新pushup pushdown都需要int query(int L,int R,int l,int r,int rt){if (L<=l&&r<=R)return dat[rt];int mid=(l+r)>>1;pushdown(rt,l,r);//若更新只有点更新,不需要这句int ANS=0;if (L<mid) ANS+=query(L,R,l,mid,rt<<1);if (R>mid) ANS+=query(L,R,mid,r,rt<<1|1);return ANS;}//查询只需要pushdownint main(){cin>>s;int n_=s.size();for(int i=1;i<=n_;i++)a[i]=s[i-1]-'0';n=1;while(n<n_)n*=2;build(1,n+1,1);memset(lazy,-1,sizeof(lazy));int m;scanf("%d",&m);for(int i=1;i<=m;i++){int l,r,flag;scanf("%d %d %d",&l,&r,&flag);int t=query(l,r+1,1,1+n,1);if(flag==0){update(l,r+1-t,1,1+n,1,0);update(r-t+1,r+1,1,1+n,1,1);}else{update(l,l+t,1,1+n,1,1);update(l+t,r+1,1,1+n,1,0);}}for(int i=1;i<=n_;i++)printf("%d",query(i,i+1,1,1+n,1));}这个是之前的模板#include<cstdio>using namespace std;int n,p,a,b,m,x,y,ans;struct node{int l,r,w,f;}tree[400001];inline void build(int k,int ll,int rr)//建树{tree[k].l=ll,tree[k].r=rr;if(tree[k].l==tree[k].r){scanf("%d",&tree[k].w);return;}int m=(ll+rr)/2;build(k*2,ll,m);build(k*2+1,m+1,rr);tree[k].w=tree[k*2].w+tree[k*2+1].w;}inline void down(int k)//标记下传{tree[k*2].f+=tree[k].f;tree[k*2+1].f+=tree[k].f;tree[k*2].w+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);tree[k*2+1].w+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1); tree[k].f=0;}inline void ask_point(int k)//单点查询{if(tree[k].l==tree[k].r){ans=tree[k].w;return ;}if(tree[k].f) down(k);int m=(tree[k].l+tree[k].r)/2;if(x<=m) ask_point(k*2);else ask_point(k*2+1);}inline void change_point(int k)//单点修改{if(tree[k].l==tree[k].r){tree[k].w+=y;return;}if(tree[k].f) down(k);int m=(tree[k].l+tree[k].r)/2;if(x<=m) change_point(k*2);else change_point(k*2+1);tree[k].w=tree[k*2].w+tree[k*2+1].w;}inline void ask_interval(int k)//区间查询{if(tree[k].l>=a&&tree[k].r<=b){ans+=tree[k].w;return;}if(tree[k].f) down(k);int m=(tree[k].l+tree[k].r)/2;if(a<=m) ask_interval(k*2);if(b>m) ask_interval(k*2+1);}inline void change_interval(int k)//区间修改{if(tree[k].l>=a&&tree[k].r<=b){tree[k].w+=(tree[k].r-tree[k].l+1)*y;tree[k].f+=y;return;}if(tree[k].f) down(k);int m=(tree[k].l+tree[k].r)/2;if(a<=m) change_interval(k*2);if(b>m) change_interval(k*2+1);tree[k].w=tree[k*2].w+tree[k*2+1].w;}int main(){scanf("%d",&n);//n个节点build(1,1,n);//建树scanf("%d",&m);//m种操作for(int i=1;i<=m;i++){scanf("%d",&p);ans=0;if(p==1){scanf("%d",&x);ask_point(1);//单点查询,输出第x个数printf("%d",ans);}else if(p==2){scanf("%d%d",&x,&y);change_point(1);//单点修改}else if(p==3){scanf("%d%d",&a,&b);//区间查询ask_interval(1);printf("%d\n",ans);}else{scanf("%d%d%d",&a,&b,&y);//区间修改 change_interval(1);}}}。
线段树

线段树目录定义基本结构实现代码树状数组编辑本段定义区间在[1,5]内的线段树线段树又称区间树,是一种对动态集合进行维护的二叉搜索树,该集合中的每个元素 x 都包含一个区间 Interval [ x ]。
线段树支持下列操作:Insert(t,x):将包含区间 int 的元素 x 插入到树中;Delete(t,x):从线段树 t 中删除元素 x;Search(t,i):返回一个指向树 t 中元素 x 的指针。
编辑本段基本结构线段树是建立在线段的基础上,每个结点都代表了一条线段[a , b]。
长度为1的线段称为元线段。
非元线段都有两个子结点,左结点代表的线段为[a , (a + b ) / 2],右结点代表的线段为[( a + b ) / 2 , b]。
右图就是一棵长度范围为[1 , 5]的线段树。
长度范围为[1 , L] 的一棵线段树的深度为log ( L - 1 ) + 1。
这个显然,而且存储一棵线段树的空间复杂度为O(L)。
线段树支持最基本的操作为插入和删除一条线段。
下面以插入为例,详细叙述,删除类似。
将一条线段[a , b] 插入到代表线段[l , r]的结点p中,如果p不是元线段,那么令mid=(l+r)/2。
如果b<mid,那么将线段[a , b] 也插入到p的左儿子结点中,如果a>mid,那么将线段[a , b] 也插入到p的右儿子结点中。
插入(删除)操作的时间复杂度为O (Log N)。
上面的都是些基本的线段树结构,但只有这些并不能做什么,就好比一个程序有输入没输出,根本没有任何用处。
最简单的应用就是记录线段有否被覆盖,并随时查询当前被覆盖线段的总长度。
那么此时可以在结点结构中加入一个变量int count;代表当前结点代表的子树中被覆盖的线段长度和。
这样就要在插入(删除)当中维护这个count值,于是当前的覆盖总值就是根节点的count值了。
另外也可以将count换成bool cover;支持查找一个结点或线段是否被覆盖。
线段树详解及例题

线段树详解及例题一、线段树的概念线段树(Segment Tree)是一种用于解决区间查询问题的数据结构,常用于静态区间查询和动态区间查询,也被称为区间树。
二、线段树的构建线段树是一棵二叉树,其每个节点都代表一个区间。
首先,我们将待处理的区间按照二叉树的方式进行划分,生成一棵满二叉树。
这里我们以求一段区间内元素的和为例:(1)首先,将整个区间 $[1,n]$ 分为两个部分,设左边区间为$[1,mid]$,右边区间为 $[mid+1,n]$。
这里的 $mid$ 是 $(1+n)/2$ 的值。
(2)然后,将左区间和右区间再分别划分成两个子区间并进行相加,直到区间大小为 1,构建出一棵完整的线段树。
三、线段树的维护构建好线段树之后,我们需要对其进行维护,以便能够快速地查询给定区间的值。
设线段树中某个节点代表区间 $[l,r]$,那么这个节点的值等于 $[l,r]$ 区间中所有元素的和。
如果需要对线段树进行更新,我们可以利用递归的方式向下更新每个节点的值。
当需要将 $[l,r]$ 区间中的某个元素修改为 $x$ 时,我们可以将其视为将线段树上区间 $[l,r]$ 的值都减去原来元素的值再加上 $x$。
四、线段树的查询线段树的查询包括单点查询和区间查询两种方式:(1)单点查询:即查询线段树中某个节点所代表的区间的值。
(2)区间查询:即查询线段树中 $[l,r]$ 区间内所有元素的和。
五、应用实例下面通过几道例题来说明线段树的应用。
问题一:给定一个序列,更新其中某个元素的值,并求出其区间和。
样例输入:8 1 3 -4 2 8 10 9 62 5 2样例输出:17问题二:对一个序列进行 $m$ 次操作,每次操作为在 $L$ 到 $R$ 的区间内加上 $c$,并输出最终改变后的序列。
样例输入:5 31 3 23 5 32 4 1样例输出:2 5 4 0 2以上就是关于线段树的详细介绍和几个应用示例,希望可以对读者有所帮助。
线段树(lazy标记)

线段树(lazy标记)1 # include<cstdio>2 # include<iostream>34using namespace std;56 # define MAX 1000047 # define lid id<<18 # define rid id<<1|1910 typedef long long LL;11int n;1213struct Segtree14 {15int l,r;16 LL lazy,sum;17 inline int len()18 {19return r-l+1;20 }21 }tree[MAX*4];2223int a[MAX];2425void push_up( int id )26 {27 tree[id].sum = tree[rid].sum+tree[lid].sum;28 }2930void push_down( int id )31 {32if ( tree[id].lazy==0 )33return;34 tree[lid].lazy += tree[id].lazy;35 tree[rid].lazy += tree[id].lazy;36 tree[lid].sum += tree[id].lazy*tree[lid].len();37 tree[rid].sum += tree[id].lazy*tree[rid].len();38 tree[id].lazy = 0;39 }4041void build( int id,int l,int r )42 {43 tree[id].l = l; tree[id].r = r;44if ( l==r )45 {46 tree[id].sum = a[l];47return;48 }49int mid = (tree[id].l+tree[id].r)/2;50 build(lid,l,mid);51 build(rid,mid+1,r);52 push_up(id);53 }5455void update( int id,int l,int r,int val )56 {57if ( tree[id].l==l&&tree[id].r==r )58 {59 tree[id].lazy += val;60 tree[id].sum += 1LL*val*tree[id].len();61return;62 }63 push_down(id);64int mid = ( tree[id].l+tree[id].r )/2;65if ( r <= mid )66 update(lid,l,r,val);67else if ( l > mid )68 update(rid,l,r,val);69else70 {71 update(lid,l,mid,val);72 update(rid,mid+1,r,val);73 }74 push_up(id);75 }7677 LL query( int id,int l,int r )78 {79if ( tree[id].l==l&&tree[id].r==r )80 {81return tree[id].sum;82 }83 push_down(id);84int mid = ( tree[id].l+tree[id].r )/2;85if ( r <= mid )86return query(lid,l,r);87else if ( l > mid )88return query(rid,l,r);89else90return query(lid,l,mid)+query(rid,mid+1,r); 91 }9293int main(void)94 {95while ( scanf("%d",&n)!=EOF )96 {97for ( int i = 1;i <= n;i++ )98 {99 scanf("%d",&a[i]);100 }101 build(1,1,n);102int q; scanf("%d",&q);103while( q-- )104 {105int ll,rr,t3; scanf("%d%d%d",&ll,&rr,&t3); 106 update(1,ll,rr,t3);107 LL ans = query(1,ll,rr);108 printf("%lld\n",ans);109 }110 }111112return0;113 }。
树状数组和线段树-电脑资料

树状数组和线段树-电脑资料一、树状数组在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i],实现:对于正整数x,定义lowbit(x)为x的二进制表达式中最右边的1所对应的值。
Lowbit(x)=xand-x对于节点i,如果它是左子节点,其父节点为i+lowbit(i);构造一个辅助数组C,其中Ci=Ai-lowbit(i)+1+Ai-lowbit(i)+2+…+Ai即C的每个元素都是A数组中的一段连续和。
具体是每个灰色节点i都属于一个以它自身结尾的水平长条,这个长条中的数之和就是Ci。
如C12=A9+A10+A11+A12=C10+A11+A12如何更新C数组中的元素:如果修改了一个Ai,需要更新C数组中的哪些元素呢?从Ci开始往右走,边走边“往上爬”(不一定沿着树中的边爬),沿途修改所有结点对应的Ci即可。
预处理时先把C数组清空,然后执行n次add操作。
总时间为O(nlogn)如何计算前缀和Si:顺着结点i往左走,边走边“往上爬”(不一定沿着树中的边爬),把沿途经过的Ci累加起来就可以了。
对于query(L,R)=SR-SL-1。
代码:varb,c,f:array[0..100000]oflongint;ff,a:Array[0..100000]ofboolean; i,j,m,n,x,y,ans,l,r,tmp:longint; s:string;functiondfs(x:longint):longint; beginifx<=1thenexit;c[f[x]]:=c[f[x]]+c[x];dfs(f[x]);end;proceduredfs1(x:longint);begindec(c[x]);ifx<=1thenexit;dfs1(f[x]);end;proceduredfs2(x:longint);begininc(c[x]);ifx<=1thenexit;dfs2(f[x]);beginreadln(n);fillchar(ff,sizeof(ff),true); fori:=1ton-1dobeginreadln(x,y);f[y]:=x;inc(b[x]);end;fori:=1tondoc[i]:=1;fori:=1tondobeginifb[i]=0thendfs(i);end;readln(m);fori:=1tomdobeginx:=0;readln(s);ifs[1]='Q'thenforj:=3tolength(s)dox:=x*10+ord(s[j])-ord('0');writeln(c[x]);end;ifs[1]='C'thenbeginforj:=3tolength(s)dox:=x*10+ord(s[j])-ord('0');ifff[x]thendfs1(x)elsedfs2(x);ff[x]:=notff[x];end;end;End.二、线段树1,.线段树的结构:区间:用一对数a和b表示一个前闭后开的区间[a,b)。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
线段树应用举例
void BuildTree(int root , int L, int R) { tree[root].L = L; tree[root].R = R; tree[root].minV = INF; tree[root].maxV = - INF; if( L != R ) { BuildTree(2*root+1,L,(L+R)/2); BuildTree(2*root+2,(L+R)/2 + 1, R); } }
AiAi+1……Aj的和是多少。 希望第2个操作每次能在log(n)时间内完成。
12
线段树应用举例
显然, [1,n]就是根节点对应的区间。
可以在每个节点记录该节点对应的区间里面的数的和Sum。
对于操作1:因为序列里面Ai最多只会被线段树的log(n)个节点
覆盖。只要求对线段树覆盖Ai的节点的Sum进行加操作,因此复 杂度是log(n)
Inc清0,接下来再往下查询。 Inc往下带的过程也是区间分解
的过程,复杂度也是O(log(n))。
26
线段树应用举例
27
线段树应用举例
28
线段树应用举例
29
线段树应用举例
#include <iostream> using namespace std; struct CNode { int L ,R; CNode * pLeft, * pRight; long long nSum; //原来的和 long long Inc; //增量c的累加 }; CNode Tree[200010]; // 2倍叶子节点数目就够 int nCount = 0; int Mid( CNode * pRoot) { return (pRoot->L + pRoot->R)/2; }
9
线段树的构建
function 以节点v为根建树、 v对应区间为[l,r] { 对节点v初始化 if (l!=r) { 以v的左孩子为根建树、区间为[l,(l+r)/2] 以v的右孩子为根建树、区间为[(l+r)/2+1,r] } } 建树的时间复杂度是O(n), n为根节点对应的区间长度。
10
线段树的基本用途
16
线段树应用举例
也可以不要左右节点指针,用一个数组存放线段树。根节点下 标为0。假设线段树上某节点下标为i,则: 左子节点下标为 i*2+1, 右子节点下标为 i*2+2 如果用一维数组存放线段树,且根节点区间[1,n] 使用左右节点指针,则数组需要有2n-1个元素。 不使用左右节点指针,则数组需要有: 2*2[log2n] -1个元素 ([log2n]向上取整) 2*2[log2n] -1 <= 4n -1 , 实际运用时常可以更小,可尝试 3
如果不是,则:对于区间[L,R],取mid=(L+R)/2; 然后看 要查询的区间与[L,mid]或[mid+1,R]哪个有交集,就进入哪个 区间进行进一步查询。 因为这个线段树的深度最深的logN,所以每次遍历操作都在 logN的内完成。但是常数可能很大。
14
线段树应用举例
如果是对区间所对应的一些数据进行修改,过程和查询 类似。 用线段树解题,关键是要想清楚每个节点要存哪些信息 (当然区间起终点,以及左右子节点指针是必须的),以及 这些信息如何高效更新,维护,查询。不要一更新就更新到
21
线段树应用举例
void Query(int root,int s,int e) //查询区间[s,e]中的最小值和最大值,如果更优就记在全局变量里 { if( tree[root].minV >= minV && tree[root].maxV <= maxV ) return; if( tree[root].L == s && tree[root].R == e ) { minV = min(minV,tree[root].minV); maxV = max(maxV,tree[root].maxV); return ;} if( e <= tree[root].Mid()) Query(2*root+1,s,e); else if( s > tree[root].Mid() ) Query(2*root+2,s,e); else { Query(2*root+1,s,tree[root].Mid()); Query(2*root+2,tree[root].Mid()+1,e); } }
对于操作2:同样只需要找到区间所覆盖的“终止”节点,然 后把所找“终止”节点的Sum累加起来。因为这些节点的数量是 O(log(n)) 的,所以这一步的复杂度也是log(n)。
13
线段树应用举例
如果走到节点[L,R]时Байду номын сангаас如果要查询的区间就是[L,R] (求AL
到AR的和)那么直接返回该节点的Sum,并累加到总的和上;
叶子节点,那样更新效率最坏就可能变成O(n) 的了。
先建树,然后插入数据,然后更新,查询。
15
线段树应用举例
例题:POJ 3264 Balanced Lineup 给定Q (1 ≤ Q ≤ 200,000)个数A1,A2 … AQ,多次求任一区间 [Ai,Aj]中最大数和最小数的差。 本题树节点结构是什么? 本题树节点结构: struct CNode { int L,R; //区间起点和终点 int minV,maxV; //本区间里的最大最小值 CNode * pLeft, * pRight; };
25
线段树应用举例
在增加时,如果要加的区间正好覆盖一个节点,则增加其
节点的Inc值,不再往下走,否则要更新nSum(加上本次增量),
再将增量往下传。这样更新的复杂度就是O(log(n))。
在查询时,如果待查区间不是正好覆盖一个节点,就将节
点的Inc往下带,然后将Inc代表的所有增量累加到nSum上后将
8
线段树的特征
1、线段树的深度不超过log2(n)+1(向上取整, n是 根节点对应区间的长度)。 2、线段树上,任意一个区间被分解后得到的“终止 节点”数目都是log(n)量级。 线段树上更新叶子节点和进行区间分解时间复杂度都 是O(log(n))的。 这些结论为线段树能在O(log(n))的时间内完成插入数 据,更新数据、查找、统计等工作,提供了理论依据.
22
线段树应用举例
int main() { int n,q,h; int i,j,k; scanf("%d%d",&n,&q); BuildTree(0,1,n); for( i = 1;i <= n;i ++ ) { scanf("%d",&h); Insert(0,i,h);} for( i = 0;i < q;i ++ ) { int s,e; scanf("%d%d", &s,&e); minV = INF;maxV = -INF;Query(0,s,e);printf("%d\n",maxV - minV); } return 0; }
20
线段树应用举例
void Insert(int root, int i,int v) //将第i个数,其值为v,插入线段树 { if( tree[root].L == tree[root].R ) { //成立则亦有 tree[root].R == i tree[root].minV = tree[root].maxV = v; return; } tree[root].minV = min(tree[root].minV,v); tree[root].maxV = max(tree[root].maxV,v); if( i <= tree[root].Mid() ) Insert(2*root+1,i,v); else Insert(2*root+2,i,v); }
5
线段树
4.叶子节点的数目和根节点表示区间的长度相同。
5.线段树节点要么0度,要么2度,因此若叶子节点数目为N
, 则线段树总结点数目为2N-1。
6.任两个结点要么是包含关系要么没有公共部分,不可能部
分重叠。
7.给定一个叶子p,从根到p路径上所有结点(即p的所有直系 祖先)代表的区间都包含点p,且其他结点代表的区间都不包含 点p。
17
线段树应用举例
Sample Input 6 3 //6个数, 3次查询 1 7 3 4 2 5 15 46 22 Sample Output 6 3 0
18
线段树应用举例
#include <iostream> using namespace std; const int INF = 0xffffff0; int minV = INF; int maxV = -INF; struct Node //不要左右子节点指针的做法 { int L, R; int minV,maxV; int Mid() { return (L+R)/2;} }; Node tree[800010]; //4倍叶子节点的数量就够
23
线段树应用举例
POJ 3468 A Simple Problem with Integers
给定Q (1 ≤ Q ≤ 100,000)个数A1,A2 … AQ,
以及可能多次进行的两个操作:
1) 对某个区间Ai … Aj的每个数都加n(n可变)
2) 求某个区间Ai … Aj的数的和
本题树节点要存哪些信息?只存该区间的数的和,行 不行?