博客
关于我
线段树
阅读量:649 次
发布时间:2019-03-15

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

线段树原理

将[1,n]分解成若干特定的自取件(数量不超过4*n),然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计.

如图表示:
在这里插入图片描述

线段树属性

  • 每个区间的长度是区间内整数的个数;
  • 叶子节点长度为1,不能再分;
  • 若一个结点对应的区间是[a,b],则其子区间对应的节点分别是[a,(a+b)/2]和[(a+b)/2+1,b];
  • 线段树的高度是; l o g 2 ( b − a + 1 ) log_2(b-a+1) log2(ba+1)+1;
  • 线段树把区间上的任意一条线段都分成不超过2 l o g 2 N log_2N log2N

线段树的定义

第一种方法

#define maxn 100007		//元素总个数int SegTree[maxn<<2];	//线段树//int Lazy[maxn<<2];	//延迟更新标记int A[maxn];			//原始数组

第二种方法

用结构体数组表示

#define maxn 100007int A[maxn];struct SegTreeNode{	int val;			//节点值	//int lazy;			//懒惰标记,又称延迟更新标记}SegTree[maxn<<2];		//定义线段树

线段树的构造

// PushUp函数更新结点信息,这里以求和为例void PushUp(int rt){SegTree[rt].val = SegTree[rt<<1].val + SegTree[rt<<1|1].val;}void build(int l,int r,int rt) 	//构造根为rt,A区间为[1,r]线段树{	if(l==r){			//叶子节点	SegTree[rt].val=A[l];	return;	}	int m = (l+r)/2;	build(l,m,rt*2);		//递归构造左子树	build(m+1,r,rt*2+1);	//递归构造右子树	PushUp(rt);		//回溯,向上更新}

单点更新

假设原始数组A[L]+=c

//l,r表示当前结点区间,rt表示当前线段树的根节点编号void Update(int L,int C,int l,int r,int rt){	if(l==r){	//叶节点直接修改	SegTree[rt].val += C;	return ;	}	int m=(l+r)>>1;	if(L <= m)	Update(L,C,l,m,rt<<1);	else	Update(L,C,m+1,r,rt<<1|1);	PushUp(rt);	//回溯,向上更新}

区间查询

//[L,R]表示操作区间,[l,r]表示当前区间,rt表示当前节点编号int Query(int L,int R,int l,int r,int rt){	if(L<=1 && r <= R)	return SegTree[rt].val;	//在区间内直接返回	if(L > r || R < 1)	return 0;	int m = ( l + r ) >> 1;	int ANS = 0;	if(L <= m) ANS+=Query(L,R,l,m,rt<<1);	//左子区间与[L,R]有重叠,递归	if(R > m) ANS += Query(L,R,m+1,r,rt<<1|1);	//右子区间与[L,R]有重叠,递归	return ANS;	// return Query(L,R,l,m,rt<<1)+Query(L,R,m+1,r,rt<<1|1);//与上面递归部分等价}

线段树的区间更新

区间更新:

是指更新某个