线段树の一 区间和
Posted Wider Gao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树の一 区间和相关的知识,希望对你有一定的参考价值。
线段树の一 区间和
具体线段树讲解:(搬运)http://blog.csdn.net/zearot/article/details/48299459
一:线段树基本概念
1:概述
线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!
性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍
2:基本操作
线段树的主要操作有:
(1):线段树的构造 void build(int node, int begin, int end);
主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值
(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],不难直接找到对应的节点回答这一问题。但并不是所有的提问都这么容易回答,比如[0,3],就没有哪一个节点记录了这个区间的最小值。当然,解决方法也不难找到:把[0,2]和[3,3]两个区间(它们在整数意义上是相连的两个区间)的最小值“合并”起来,也就是求这两个最小值的最小值,就能求出[0,3]范围的最小值。同理,对于其他询问的区间,也都可以找到若干个相连的区间,合并后可以得到询问的区间。
(3):区间或节点的更新 及 线段树的动态维护update (这是线段树核心价值所在,节点中的标记域可以解决N多种问题)
动态维护需要用到标记域,延迟标记等。
a:单节点更新
b:区间更新(线段树中最有用的)
需要用到延迟标记,每个结点新增加一个标记,记录这个结点是否被进行了某种修改操作(这种修改操作会影响其子结点)。对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些结点标上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个结点p,并且决定考虑其子结点,那么我们就要看看结点p有没有标记,如果有,就要按照标记修改其子结点的信息,并且给子结点都标上相同的标记,同时消掉p的标记。(优点在于,不用将区间内的所有值都暴力更新,大大提高效率,因此区间更新是最优用的操作)
3:主要应用
(1):区间最值查询问题
(2):连续区间修改或者单节点更新的动态查询问题
(3):多维空间的动态查询
/******************************* 线段树V1.0 支持区间加、区间和查询 ********************************/ #include<iostream> #include<cstdio> #include<cstring> #define N 1000010 using namespace std; struct node { int left;//节点所代表区间左端 int right;//节点所代表区间右端 long long sum;//区间和 long long add;//Lazy标记 } tree[N]; long long a[N]; void Build_Tree(int left,int right,int node)//left:当前区间左端 right:当前区间右端 node:当前节点 { tree[node].left=left; tree[node].right=right; if(left==right) tree[node].sum=a[left]; else { int mid=(left+right)>>1; Build_Tree(left,mid,node<<1); Build_Tree(mid+1,right,node<<1|1); tree[node].sum=tree[node<<1].sum+tree[node<<1|1].sum; } } void Push_Down(int node)//标记下放 node:当前节点 { if(tree[node].add==0) return; tree[node<<1].add+=tree[node].add; tree[node<<1|1].add+=tree[node].add; tree[node<<1].sum+=tree[node].add*(tree[node<<1].right-tree[node<<1].left+1); tree[node<<1|1].sum+=tree[node].add*(tree[node<<1|1].right-tree[node<<1|1].left+1); tree[node].add=0; } void Push_Up(int node)//上推 node:当前节点 { tree[node].sum=tree[node<<1].sum+tree[node<<1|1].sum; } void Add_Range(int left,int right,int node,long long value)//区间加操作 left:操作区间左端点 right:操作区间右端点 node:当前节点 value:操作值 { if(tree[node].left>=left&&tree[node].right<=right) { tree[node].add+=value; tree[node].sum+=value*(tree[node].right-tree[node].left+1); return; } Push_Down(node); int mid=(tree[node].left+tree[node].right)>>1; if(left<=mid) Add_Range(left,right,node<<1,value); if(right>mid) Add_Range(left,right,node<<1|1,value); Push_Up(node); } long long Query_Sum(int left,int right,int node)//区间和查询 left:查询区间左端点 right:查询区间右端点 node:当前节点 { if(tree[node].left>right||tree[node].right<left) return 0; Push_Down(node); if(tree[node].left>=left&&tree[node].right<=right) return tree[node].sum; return Query_Sum(left,right,node*2)+Query_Sum(left,right,node*2+1); } int main() { int n,m;//n:区间大小 m:操作次数 cin>>n>>m; for(int i=1; i<=n; i++) cin>>a[i]; Build_Tree(1,n,1); while(m--) { int x,y,c; cin>>c>>x>>y; if(c==2)//查询 cout<<Query_Sum(x,y,1)<<endl; if(c==1) //操作 { long long v; cin>>v; Add_Range(x,y,1,v); } } return 0; }
以上是关于线段树の一 区间和的主要内容,如果未能解决你的问题,请参考以下文章
AcWing 1264. 动态求连续区间和(线段树区间查询模板)