基本线段树及动态开点

  • Post author:
  • Post category:其他




前言

线段树是算法竞赛中常用的用来维护

区间信息

的数据结构。

线段树可以在



O

(

l

o

g

N

)

O(logN)






O


(


l


o


g


N


)





的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。



简介

线段树将每个长度不为



1

1






1





的区间划分成左右两个区间递归求解,把整个线段划分为一个树形结构,通过合并左右两区间信息来求得该区间的信息。这种数据结构可以方便的进行大部分的区间操作。

线段树的结构如下:

在这里插入图片描述

简单的来讲,对于线段树内任意一颗非叶子结点而言,他表示的区间是



[

l

,

r

]

[l,r]






[


l


,




r


]





,其左儿子表示的区间是



[

l

,

m

i

d

]

[l, mid]






[


l


,




mi


d


]





,其右儿子表示的区间是



[

m

i

d

+

1

,

r

]

[mid+1, r]






[


mi


d




+








1


,




r


]





,其中



m

i

d

=

l

+

r

2

mid=\lfloor\frac{l+r}2\rfloor






mi


d




=























2
















l


+


r





























堆式存储



空间

如果



n

n






n





为叶子结点数量,那么数组要开



2

l

o

g

(

n

)

+

1

2^{log(n)+1}







2











l


o


g


(


n


)


+


1













这么大,如果懒得算就开直接 4n。



建树

考虑递归建树,dfs遍历一遍即可,要在回溯时pushup(即维护当前区间的值,因为下面更改了)。



区间修改

单点修改相当于



l

=

r

l = r






l




=








r





的情况 , 我就不啰嗦了。

可以考虑直接修改表示区间被



[

l

,

r

]

[l,r]






[


l


,




r


]





包含的所有节点。但如果这样做时间肯定爆,所以我们采用



l

a

z

y

t

a

g

lazytag






l


a


zy


t


a


g





技术。

就是懒标记拉。我们令



t

a

g

[

k

]

tag[k]






t


a


g


[


k


]





表示节点



k

k






k





欠下其子树的更新值,区间修改时点到即止(即不去更新被完全包含的结点的子树),然后呢什么时候访问到这个结点再下传标记给它的子节点(注意修改时也要下传,因为如果不下传标记,子节点会pushup一个错误额值)。

我们可以记下传标记为pushdown,这个操作实在接着向下递归之前。



区间查询

直接将所有被



[

l

,

r

]

[l,r]






[


l


,




r


]





包含的区间合并起来即可,记得下传标记。



模板(区间和)

struct SegamentTree {
	static const int MAX = 1e5;
	struct TreeNode {
		ll val, tag;
	}tr[MAX<<2];
	inline void pushup(int &k) {
		tr[k].val = tr[k<<1].val + tr[k<<1|1].val;
	}
	inline void pushdown(int &k, int &l, int &r, int &mid) {
		if(tr[k].tag) {
			tr[k<<1].val += tr[k].tag * (mid-l+1), tr[k<<1|1].val += tr[k].tag * (r-mid);
			if(l < mid) tr[k<<1].tag += tr[k].tag, tr[k<<1|1].tag += tr[k].tag; // 叶子结点不需要标记
			tr[k].tag = 0;
		}
	}
	void build(int k, int l, int r) { 
    // k:结点编号 [l, r] 结点k的表示的区间。
		if(l == r) {
			tr[k].val = a[l];
			return;
		}
		int mid = l + (r - l >>1);
		build(k<<1, l, mid);
		build(k<<1|1, mid+1, r);
		pushup(k);
	}
	ll query(int k, int l, int r, int x, int y) { 
    // k, l, r 同上 [x, y] 查询区间
		if(x <= l && r <= y) // 如果被包含
			return tr[k].val;
		int mid = l + (r - l >> 1); ll res = 0;
		pushdown(k, l, r, mid);
		if(x <= mid) res = query(k<<1, l, mid, x, y); // 左区间与查询区间有交集
		if(y > mid) res += query(k<<1|1, mid+1, r, x, y); // 右区间与查询区间有交集
		return res;
	}
	void update(int k, int l, int r, int x, int y, int t) { 
    // k, l, r, x, y 同上 t 表示增加值
		if(x <= l && r <= y) { 
			tr[k].val += (r - l + 1) * t;
			if(l != r) tr[k].tag += t;
			return;
		}
		int mid = l + (r - l >> 1);
		pushdown(k, l, r, mid);
		if(x <= mid) update(k<<1, l, mid, x, y, t); 
		if(y > mid) update(k<<1|1, mid+1, r, x, y, t);
		pushup(k);
	}
};



标记永久化

标记永久化就是不上下传递标记,在不爆的前提下可以优化常数,也更加方便使用。

修改时,把所有不被修改区间完全包含和被修改区间一级包含(即第一个被枚举到的完全包含区间)结点累计上他总共能获得的贡献,然后在一级包含结点上打标记。

查询时,累计枚举到一级包含结点前的所有tag,返回



t

a

g

s

u

m

l

e

n

tagsum*len






t


a


g


s


u


m













l


e


n





,其中



t

a

g

s

u

m

tagsum






t


a


g


s


u


m





指标记和,



l

e

n

len






l


e


n





指的是这一节点表示的区间的长度。



Code

ll query(int k, int l, int r, int x, int y, ll sum) {
		if(x <= l && r <= y)
			return tr[k].val + sum * (r - l + 1);
		sum += tr[k].tag;
		int mid = l + (r - l >> 1); ll res = 0;
		if(x <= mid) res = query(k<<1, l, mid, x, y, sum);
		if(y > mid) res += query(k<<1|1, mid+1, r, x, y, sum);
		return res;
	}
	void update(int k, int l, int r, const int& x, const int& y, const int& t) {
		tr[k].val += (std::min(y, r) - std::max(x, l) + 1) * t; // 他应得的贡献
		if(x <= l && r <= y) {
			tr[k].tag += t;
			return;
		}
		int mid = l + (r - l >> 1);
		if(x <= mid) update(k<<1, l, mid, x, y, t);
		if(y > mid) update(k<<1|1, mid+1, r, x, y, t);
	}



动态开点

动态开点就是不一下开满整一棵树,用到哪开到哪。可以在结构体内建立ls和rs表示左右儿子分别存在哪,这里就不贴代码了。



版权声明:本文为danxiangfa原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。