浅谈如何优美地实现线段树?

前言

线段树作为一种基础的数据结构,在信息学竞赛中有着广泛的应用。

与之相伴随的是大量的基于线段树维护的题目。

在这其中,有不少题目所需要实现的线段树包含多种操作,维护多种信息,拥有较大的代码难度。

相信不少人也有过写线段树越写越乱,写大量本质相同代码的经历。

笔者在此结合平日里实现线段树的经验,斗胆浅谈一下如何准确,快速的实现线段树。

正文

首先,在笔者的平时习惯中。线段树的写法通常根据节点信息是否用结构体封装可以分为两类。

这里强烈建议读者通过练习做到熟悉两种不同的线段树写法。

从而在题目较为简单时,使用第一类线段树快速实现。

而在操作较为繁琐,询问多种信息时,则采取第二种线段树,提高代码准确度。

这里第一类线段树的实现较为简单,不在赘述,重点讲解第二类线段树的实现。

这里首先给出第二类线段树的框架

struct tag
{
	//the tag of each node
	void clear(){}
};
tag operator+=(tag &x,tag k)
{
	tag nx=x;
	/*
		...
	*/
	return x=nx;
}
struct data
{
	//the data of each node
};
data operator+(data a,data b)
{
	data ans;
	/*
		...
	*/
	return ans;
}
data operator+=(data &x,tag k)
{
	data nx=x;
	/*
		...
	*/
	return x=nx;
}
struct Segment_Tree
{
	#define lson o<<1
	#define rson o<<1|1
	#define mid ((t[o].l+t[o].r)>>1)
	tag f[N*4];
	data t[N*4];
	inline void pushup(int o){t[o]=t[lson]+t[rson];}
	inline void update(int o,tag k){f[o]+=k,t[o]+=k;}
	inline void pushdown(int o){update(lson,f[o]);update(rson,f[o]);f[o].clear();}
	void build(int o,int l,int r)
	{
		f[o].clear();t[o].l=l;t[o].r=r;t[o].len=r-l+1;
		if(l==r)return (void)(t[o]={l,r,r-l+1});
		build(lson,l,mid);build(rson,mid+1,r);pushup(o);
	}
	void opt(int o,int ql,int qr,tag k)
	{
		if(ql<=t[o].l&&t[o].r<=qr)return update(o,k);
		pushdown(o);
		if(ql<=mid)opt(lson,ql,qr,k);
		if(qr>mid)opt(rson,ql,qr,k);
		pushup(o);
	}
	data query(int o,int ql,int qr)
	{
		if(ql<=t[o].l&&t[o].r<=qr)return t[o];
		pushdown(o);
		bool fg1=ql<=mid,fg2=qr>mid;
		if(fg1&&fg2)return query(lson,ql,qr)+query(rson,ql,qr);
		else
		{
			if(fg1)return query(lson,ql,qr);
			if(fg2)return query(rson,ql,qr);
		}
	}
}T;

简单介绍一下实现的思想。
首先将每个节点所存储的信息分为\(tag\)\(data\),分别对应"懒标记"和"维护信息"。
然后我们需要做的便是重载以下三种运算符

\(1.tag+=tag\)
\(2.data+data\)
\(3.data+=tag\)

这三种运算符分别对应了
1.父节点的标记作用于子节点的标记
2.左儿子信息与右儿子信息的合并
3.父节点的标记作用于子节点的存储信息

有了这三种运算符以后,我们便将几乎一切线段树的实现问题转化为了定义这三种运算+复制模板这一问题。

posted @ 2021-05-11 22:24  Creed-qwq  阅读(207)  评论(0编辑  收藏  举报