线段树の一 区间和

线段树の一 区间和

具体线段树讲解:(搬运)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;
}

  

posted @ 2017-08-11 12:00  银河渡舟  阅读(236)  评论(0编辑  收藏  举报