浅谈线段树 Segment Tree

Part I.Introduction

        线段树是一棵二叉树,其每个节点表示一个区间[a,b]。

        线段树的作用是进行区间加、修改、询问等操作。

        若一个节点不是叶子节点,则其左儿子表示区间[a,mid],其右儿子表示区间[mid+1,r]。

        由于这是一棵二叉树,我们可以采用一个数组记录一棵线段树。设某个节点数组下标为i,则其左儿子数组下标为i*2,右儿子数组下标为i*2+1。

        我们可以发现,线段树除去最后一层可以看做是一棵满二叉树。由二叉树的性质可得,我们需开的空间为4*n。

        统一起见,本文中讨论的操作为区间加和区间询问最大值。

        我们可以使用以下的定义:

#define LeftSon(x) ((x)*2)
#define RightSon(x) ((x)*2+1)

struct Segment_Tree{
	int Max,add;
}tree[maxn*4];

如图即为一棵[1,3]的线段树。



Part II.Build

        我们可以用递归的方法建立一棵线段树。

void build(int k,int lc,int rc){
	if (lc==rc){
		tree[k].Max=seq[lc];
		return;
	}
	int mid=(lc+rc)/2;
	build(LeftSon(k),lc,mid);
	build(RightSon(k),mid+1,rc);
	update(k);
}


Part III.Lazy

        Lazy思想是一种应用广泛的思想。

        在线段树中,如果我们要对一个节点所代表的区间进行修改,我们只需修改这个节点本身,不必再修改其左右子树,而是待下次操作时将标记下传至左右儿子即可。

        统一起见,我们定义的Lazy标记的含义为该节点的信息已经修改的情况、但还未下传的Lazy标记。


Part IV.Update

        Update操作的作用是用一个节点的2个儿子的信息来更新其信息。

        Update操作可以写为:

void update(int k){
	tree[k].Max=max(tree[LeftSon(k)].Max,tree[RightSon(k)].Max);
}

Part V.Download

        Download操作的作用是把一个节点的Lazy标记下传至其左右儿子。

        需要注意的是,下传标记时我们需要更改左右儿子的答案标记和左右儿子的Lazy标记。

        Download操作可以写为:

void download(int k){
	if (tree[k].add){
		tree[LeftSon(k)].add+=tree[k].add;
		tree[RightSon(k)].add+=tree[k].add;
		tree[LeftSon(k)].Max+=tree[k].add;
		tree[RightSon(k)].Max+=tree[k].add;
		tree[k].add=0;
	}
}

Part VI.Add

        我们可以发现线段树的一个性质:一个区间的信息可以由若干个子区间的信息合并得到。(*)

        由此,Add操作可以不断将一个区间分解为左右2个子区间,直到一个区间恰好为一个节点所代表的区间。然后我们将这些节点的lazy标记加上需要加的值就可以了。

        Add操作可以写为:

void add(int k,int lc,int rc,int l,int r,int d){
	if (lc==l && rc==r){
		tree[k].Max+=d;
		tree[k].add+=d;
		return;
	}
	download(k);
	int mid=(lc+rc)/2;
	if (r<=mid) add(LeftSon(k),lc,mid,l,r,d);
	else if (l>mid) add(RightSon(k),mid+1,rc,l,r,d);
	else{
		add(LeftSon(k),lc,mid,l,mid,d);
		add(RightSon(k),mid+1,rc,mid+1,r,d);
	}
	update(k);
}

Part VII.Ask

        由Part VI中的(*)性质,对于一段区间的询问可以由几个子区间的询问合并得到。

        由于是运用了同一个性质,我们可以发现Ask操作和Add操作的代码很相似。

        Ask操作的代码可以写为:

int ask(int k,int lc,int rc,int l,int r){
	if (lc==l && rc==r) return tree[k].Max;
	download(k);
	int mid=(lc+rc)/2;
	if (r<=mid) return ask(LeftSon(k),lc,mid,l,r);
	if (l>mid) return ask(RightSon(k),mid+1,rc,l,r);
	return max(ask(LeftSon(k),lc,mid,l,mid),ask(RightSon(k),mid+1,rc,mid+1,r));
}


Part VIII.Analysis

        在上面我们已经知道了线段树除去最下面一层是一棵满二叉树。

        而各种操作的最坏时间复杂度是O(h),由二叉树的性质,也就是O(log n)。


Part IX.Exercise

        至此,线段树的基本操作就介绍完了。

        下面推荐2题BZOJ上的模板题:BZOJ 1012 & BZOJ 1798

        以上2题的题解均在本Blog中。


Part X.Code

/*
 * Algorithm:Segment_Tree
 * Author:PYC
 */

#include <cstdio>

#define maxn 1000000

using namespace std;

int n,q,a[maxn],tree[maxn*4],lazy[maxn*4];

inline int max(int a,int b){if (a>b) return a;return b;}

void build(int k,int l,int r){
	if (l==r){tree[k]=a[l];return;}
	int mid=(l+r)/2;
	build(k*2,l,mid);
	build(k*2+1,mid+1,r);
	tree[k]=max(tree[k*2],tree[k*2+1]);
}

void down(int x){
	lazy[x*2]+=lazy[x];
	lazy[x*2+1]+=lazy[x];
	tree[x*2]+=lazy[x];
	tree[x*2+1]+=lazy[x];
	lazy[x]=0;
}

void up(int x){tree[x]=max(tree[x*2],tree[x*2+1]);}

int ask(int k,int lc,int rc,int l,int r){
	if (lc==l && rc==r) return tree[k];
	int mid=(lc+rc)/2;
	down(k);
	if (r<=mid) return ask(k*2,lc,mid,l,r);
	if (l>mid) return ask(k*2+1,mid+1,rc,l,r);
	return max(ask(k*2,lc,mid,l,mid),ask(k*2+1,mid+1,rc,mid+1,r));
}

void add(int k,int lc,int rc,int l,int r,int d){
	if (lc==l && rc==r){lazy[k]+=d;tree[k]+=d;return;}
	int mid=(lc+rc)/2;
	down(k);
	if (r<=mid){add(k*2,lc,mid,l,r,d);up(k);return;}
	if (l>mid){add(k*2+1,mid+1,rc,l,r,d);up(k);return;}
	add(k*2,lc,mid,l,mid,d);
	add(k*2+1,mid+1,rc,mid+1,r,d);
	up(k);
}

int main(){
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;++i) scanf("%d",&a[i]);
	build(1,1,n);
	for (int i=1;i<=q;++i){
		int x;
		scanf("%d",&x);
		if (x==1){
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%d\n",ask(1,1,n,l,r));
		}
		else{
			int l,r,d;
			scanf("%d%d%d",&l,&r,&d);
			add(1,1,n,l,r,d);
		}
	}
	return 0;
}


Part XI.Thank You!

        Thank you for reading!


By Charlie Pan

Feb 19,2014

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值