线段树

线段树的结构

对于大量数据信息的查询和修改,普通数组O(n)的时间复杂度已经远远不能满足需求,为此计算机科学家们利用二分法的思想,创造出了一种巧妙的数据结构——线段树,它的查询与修改的时间复杂度为O(logn)。
线段树从字面上我们可以拆成两部分,“线段”指它是建立在区间上的,“树”指它是一个二叉树。
对于任何一个数据集,一般情况下我们会将其存在数组a[n]中。但使用线段树这种数据结构后,我们所建立的数组中每个元素存储的并不是单一的数据,而是一段区间的区间和。即 c [ i ] = ∑ j = l r a [ j ] c[i]=\sum_{j=l}^ra[j] c[i]=j=lra[j]
线段树的本质是将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个结点。
它的结构如下图。
在这里插入图片描述
对于任意的结点c[i],它的左儿子是c[i * 2] ,右儿子是c[i * 2 + 1]
若c[i]管理的区间是[a, b],则它的左儿子c[i * 2]管理的区间是[a, (a + b) / 2],它的右儿子管理的区间是[(a + b) / 2 + 1, b]
线段树的根结点c[1]管理的是整个区间。

线段树的构造与信息维护(包括最大值,最小值,区间和)

线段树的空间复杂度

设数组a的大小为n,当n不是2的幂次时,线段树c的深度为 [ l o g 2 n ] + 2 [log_2^n]+2 [log2n]+2([]为向下取整);当n是二的幂次时,线段树c的深度为 [ l o g 2 n + 1 ] [log_2^n+1] [log2n+1]。因此,当n不是2的幂次时,线段树c所需要的空间为 2 [ l o g 2 n ] + 2 − 1 2^{[log_2^n]+2}-1 2[log2n]+21,当n是2的幂次时;线段树c所需要的空间为 2 [ l o g 2 n ] + 1 − 1 2^{[log_2^n]+1}-1 2[log2n]+11
我们考虑最坏的情况,即n较大,且n不是2的幂次但十分靠近2的幂次。因为在这种情况下,线段树所需要的空间是 2 [ l o g 2 n ] + 2 − 1 2^{[log_2^n]+2}-1 2[log2n]+21,并且 [ l o g 2 n ] ≈ l o g 2 n [log_2^n]\approx log_2^n [log2n]log2n,所以所需的空间约为 4 n − 1 4n-1 4n1
因此,线段树的空间复杂度为O(4n)

线段树的构造

我们在构造线段树时需要存储每个结点所管理的区间端点信息,以便我们快速确定当前的结点管理的是哪个区间。
其实在线段树的每个结点中我们可以存储很多我们需要的信息,比如区间和,区间最大值,区间最小值等,只要这些信息满足结合律(即任意区间的信息等于其所有子区间信息之并),我们都可以通过线段树来管理并维护这些信息。
什么是满足结合律呢?
比如:一个区间的最大(小)值等于其所有子区间最大(小)值中的最大(小)值。一个区间的区间和等于其所有子区间区间和相加。
结点:

class Node
{
	public:
		int l, r;
		long long sum, max, min;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
		}
};

在构造线段树时,我们采用递归建树的方式,将上一层的一个大区间分成下一层的左右两个小区间,不断重复这个操作直到最后一层的区间中只剩下一个元素,无法再分。
递归建树:

void build(int l, int r, int i)//i指线段树c的第i个结点c[i],l指c[i]的左端点,r指c[i]的右端点。
{
	c[i].l = l;
	c[i].r = r;
	if (l == r) return;//当l = r时说明区间只剩一个元素
	build(l, (l + r) / 2, i * 2);
	build((l + r) / 2 + 1, r, i * 2 + 1);
}
int main()
{
	Node c[size * 4];
	build(1, size, 1);//先从整个区间[1, size]开始划分
}

封装成类:

//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
	public:
		int l, r;
		long long sum, max, min;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
		}
};
class SegTree : vector<Node>
{
	public:
		int size;
		explicit SegTree(int size = 0)
		{
			this->size = size > 0 ? size : 0;
			assign(this->size * 4, Node());//空间复杂度为O(4n)
			build(1, size, 1);
		}
		
	private:
		void build(int l, int r, int loc)
		{
			(*this)[loc].l = l;
			(*this)[loc].r = r;
			if (l == r) return;
			build(l, (l + r) / 2, loc * 2);
			build((l + r) / 2 + 1, r, loc * 2 + 1);
		}
};

单点修改与pushup()函数

当我们想修改a数组中的一个元素a[i]时就要修改线段树c中的若干个结点,这些结点均管理着包含a[i]的一段区间。
如下图,当我们想要修改a[4]时,就要修改c[1]、c[2]、c[5]、c[10]、c[20]
在这里插入图片描述
寻找这些结点可以用迭代的方法:从根节点开始,每次都判断i在左区间还是在右区间,然后进入线段树的下一层,重复上述操作,直到最后一层。以a[4]为例:
在这里插入图片描述
当我们迭代到最后一层时,开始进行回溯操作,从最底层开始不断修改迭代时走过的区间。
如图:
在这里插入图片描述
从底层向上层修改信息的回溯操作我们称之为pushup()函数:

void pushup(int i)
{
	c[i].max = max(c[i * 2].max, c[i * 2 + 1].max);//区间最大值等于其左右子区间最大值中的最大值
	c[i].min = min(c[i * 2].min, c[i * 2 + 1].min);//区间最小值等于其左右子区间最小值中的最小值
	c[i].sum = c[i * 2].sum + c[i * 2 + 1].sum;//区间和等于其左右子区间区间和相加
}

整个修改过程可以用递归实现:

void update(int loc, long long val, int i)
{
	if (c[i].l != c[i].r)//如果c[i]管理的区间内不只有一个元素,说明还没到达目标位置,要继续递推下去
	{
		if ((c[i].l + c[i].r) / 2 >= loc) update(loc, val, i * 2);//判断目标位置是否在左区间
		else update(loc, val, i * 2 + 1);//判断目标位置是否在右区间
		pushup(i);//回溯,更新区间信息
	}
	else//如果c[i]是叶结点(即区间内只有一个元素),说明到达目标位置
	{
		c[i].max += val;
		c[i].min += val;
		c[i].sum += val;
	}
}

封装成类:

//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
	public:
		int l, r;
		long long sum, max, min;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
		}
};
class SegTree : vector<Node>
{
	public:
		int size;
		explicit SegTree(int size = 0)
		{
			this->size = size > 0 ? size : 0;
			assign(this->size * 4, Node());
			build(1, size, 1);
		}
		void update(int loc, long long val)//单点修改
		{
			if (loc < 1 || loc > size) return;
			this->loc = loc;
			this->val = val;
			_update(1);
		}
		
	private:
		int loc;
		long long val;
		void build(int l, int r, int loc)
		{
			(*this)[loc].l = l;
			(*this)[loc].r = r;
			if (l == r) return;
			build(l, (l + r) / 2, loc * 2);
			build((l + r) / 2 + 1, r, loc * 2 + 1);
		}
		void pushup(int i)
		{
			(*this)[i].max = max((*this)[i * 2].max, (*this)[i * 2 + 1].max);
			(*this)[i].min = min((*this)[i * 2].min, (*this)[i * 2 + 1].min);
			(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2 + 1].sum;
		}
		void _update(int i)
		{
			if ((*this)[i].l != (*this)[i].r)
			{
				if (((*this)[i].l + (*this)[i].r) / 2 >= loc) _update(i * 2);
				else _update(i * 2 + 1);
				pushup(i);
			}
			else
			{
				(*this)[i].max += val;
				(*this)[i].min += val;
				(*this)[i].sum += val;
			}
		}
};

区间修改与lazy标记

在进行区间修改时,我们往往要修改线段树中的若干个结点。
在这里插入图片描述
如图,当我们想要将[1, 11]区间内全部元素加k时,就要修改线段树中的所有结点,这样时间复杂的就退化到了O(n),为了保证线段树区间修改的时间复杂度依然为O(logn),我们引入了lazy标记。当某一个区间需要整体都加k时,我们将这个区间的lazy标记加k,而不继续向下进行,省去了修改下层子区间的时间。
在这里插入图片描述
如图,当我们想要修改[1, 9]时只需修改黄色结点,而不用lazy标记需要修改的是蓝色结点。
因此结点类需要添加一个lazy标记:

class Node
{
	public:
		int l, r;
		long long sum, max, min, lazy;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
			lazy = 0;
		}
};

由于引入了lazy标记,pushup()函数也要做相应的修改。很显然,当一个区间的lazy标记为k时,这个区间的最大值为max + k,最小值为min + k,区间和为sum + (r - l + 1) * k:

void pushup(int i)
{
	c[i].max = max(c[i * 2].max + c[i * 2].lazy, c[i * 2 + 1].max + c[i * 2 + 1].lazy);
	c[i].min = min(c[i * 2].min + c[i * 2].lazy, c[i * 2 + 1].min + c[i * 2 + 1].lazy);
	c[i].sum = c[i * 2].sum + c[i * 2].lazy * (c[i * 2].r - c[i * 2].l + 1) + c[i * 2 + 1].sum + c[i * 2 + 1].lazy * (c[i * 2 + 1].r - c[i * 2 + 1].l + 1);
}

在递归更新的时候,遇到叶结点时不需要改变其lazy标记,直接更新信息。因为lazy标记代表的是这个区间内所有元素被统一加了一个数值,而叶结点管理的区间只有一个元素,不需要lazy标记。
下面是区间修改的代码:

void update(int l, int r, long long val, int i)
{
	if (c[i].l == c[i].r) 
	{
		c[i].max += val;
		c[i].min += val;
		c[i].sum += val;
	}
	else if (l <= c[i].l && c[i].r <= r) c[i].lazy += val;
	else
	{
		if ((c[i].l + c[i].r) / 2 >= l) update(l, r, val, i * 2);
		if ((c[i].l + c[i].r) / 2 < r) update(l, r, val, i * 2 + 1);
		pushup(i);
	}
}

封装成类:

//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
	public:
		int l, r;
		long long sum, max, min, lazy;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
			lazy = 0;
		}
};
class SegTree : vector<Node>
{
	public:
		int size;
		explicit SegTree(int size = 0)
		{
			this->size = size > 0 ? size : 0;
			assign(this->size * 4, Node());
			build(1, size, 1);
		}
		void update(int l, int r, long long val)//区间修改
		{
			if (l < 1 || r < l || r > size) return;
			loc_l = l;
			loc_r = r;
			this->val = val;
			_update(1);
		}
		void update(int loc, long long val)//重载一个单点修改
		{
			if (loc < 1 || loc > size) return;
			loc_l = loc_r = loc;
			this->val = val;
			_update(1);
		}
		
	private:
		int loc_l, loc_r;
		long long val;
		void build(int l, int r, int loc)
		{
			(*this)[loc].l = l;
			(*this)[loc].r = r;
			if (l == r) return;
			build(l, (l + r) / 2, loc * 2);
			build((l + r) / 2 + 1, r, loc * 2 + 1);
		}
		void pushup(int i)
		{
			(*this)[i].max = max((*this)[i * 2].max + (*this)[i * 2].lazy, (*this)[i * 2 + 1].max + (*this)[i * 2 + 1].lazy);
			(*this)[i].min = min((*this)[i * 2].min + (*this)[i * 2].lazy, (*this)[i * 2 + 1].min + (*this)[i * 2 + 1].lazy);
			(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2].lazy * ((*this)[i * 2].r - (*this)[i * 2].l + 1) + (*this)[i * 2 + 1].sum + (*this)[i * 2 + 1].lazy * ((*this)[i * 2 + 1].r - (*this)[i * 2 + 1].l + 1);
		}
		void _update(int i)
		{
			if ((*this)[i].l == (*this)[i].r) 
			{
				(*this)[i].max += val;
				(*this)[i].min += val;
				(*this)[i].sum += val;
			}
			else if (loc_l <= (*this)[i].l && (*this)[i].r <= loc_r) (*this)[i].lazy += val;
			else
			{
				if (((*this)[i].l + (*this)[i].r) / 2 >= loc_l) _update(i * 2);
				if (((*this)[i].l + (*this)[i].r) / 2 < loc_r) _update(i * 2 + 1);
				pushup(i);
			}
		}
};

单点查询与pushdown()函数

不考虑lazy标记,单点查询的操作应该是这样的:从最大的区间开始,不断地判断目标位置在左子区间还是在右子区间,然后一层一层递推下去,直至目标位置,最后返回目标位置的信息。
代码如下:

long long query(int loc, int i)
{
	if (c[i].l == c[i].r) return c[i].sum;
	else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2);
	else return query(i * 2 + 1);
}

但是在区间修改中,我们引入了lazy标记,这就导致了若lazy标记不为0,区间的信息并不是真实的。也许有人会提出:我在递归回溯的时候把每一层的lazy标记加上不就好了吗?
比如:

long long query(int loc, int i)
{
	if (c[i].l == c[i].r) return c[i].sum;
	else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2) + c[i].lazy;
	else return query(i * 2 + 1) + c[i].lazy;
}

这样的确解决了问题,但是对于区间查询来说这样的操作过于繁琐。
于是我们便引入了pushdown()函数,这个函数的作用是将lazy标记向下传递。

void pushdown(int i)
{
	c[i].sum += c[i].lazy * (c[i].r - c[i].l + 1);
	c[i].max += c[i].lazy;
	c[i].min += c[i].lazy;
	if (c[i].l != c[i].r)
	{
		c[i * 2].lazy += c[i].lazy;
		c[i * 2 + 1].lazy += c[i].lazy;
	}
	c[i].lazy = 0;
}

lazy标记设置的目的就是省去修改下层子区间的时间,而我们在修改时又不需要调用下层子区间的信息,因此就用lazy标记代替了整段区间的修改信息,不用具体到去修改每一个叶结点的信息。
但如果现在要查询的点被包含在了一段设有lazy标记的区间里,即我们现在需要调用下层子区间的信息了,我们就可以在不断的递推过程中顺道将lazy标记不断下传,更新每个区间的信息。
因此单点查询lazy标记的下传策略是:只要目标查询位置包含在当前的递推区间内,标记就下传。
在这里插入图片描述
如图,依然以修改了[1, 9]的线段树为例,现在要查询a[2]的信息。黄色结点是查询前信息被更新过的结点,蓝色结点是查询后信息被更新、标记被下传的结点,绿色结点是查询前后所有lazy标记不为0的结点。
引入pushdown()函数后的单点查询代码如下,只多了一行pushdown()函数:

long long query(int loc, int i)
{
	if (c[i].lazy != 0) pushdown(i);
	if (c[i].l == c[i].r) return c[i].sum;
	else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2);
	else return query(i * 2 + 1);
}

封装成类:

//若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
	public:
		int l, r;
		long long sum, max, min, lazy;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
			lazy = 0;
		}
};
class SegTree : vector<Node>
{
	public:
		int size;
		explicit SegTree(int size = 0)
		{
			this->size = size > 0 ? size : 0;
			assign(this->size * 4, Node());
			build(1, size, 1);
		}
		void update(int l, int r, long long val)//区间修改
		{
			if (l < 1 || r < l || r > size) return;
			loc_l = l;
			loc_r = r;
			this->val = val;
			_update(1);
		}
		void update(int loc, long long val)//重载一个单点修改
		{
			if (loc < 1 || loc > size) return;
			loc_l = loc_r = loc;
			this->val = val;
			_update(1);
		}
		long long query(int loc)//单点查询
		{
			if (loc < 1 || loc > size) return 0;
			this->loc = loc;
			return _query(1);
		}
		
	private:
		int loc_l, loc_r, loc;
		long long val;
		void build(int l, int r, int loc)
		{
			(*this)[loc].l = l;
			(*this)[loc].r = r;
			if (l == r) return;
			build(l, (l + r) / 2, loc * 2);
			build((l + r) / 2 + 1, r, loc * 2 + 1);
		}
		void pushup(int i)
		{
			(*this)[i].max = max((*this)[i * 2].max + (*this)[i * 2].lazy, (*this)[i * 2 + 1].max + (*this)[i * 2 + 1].lazy);
			(*this)[i].min = min((*this)[i * 2].min + (*this)[i * 2].lazy, (*this)[i * 2 + 1].min + (*this)[i * 2 + 1].lazy);
			(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2].lazy * ((*this)[i * 2].r - (*this)[i * 2].l + 1) + (*this)[i * 2 + 1].sum + (*this)[i * 2 + 1].lazy * ((*this)[i * 2 + 1].r - (*this)[i * 2 + 1].l + 1);
		}
		void _update(int i)
		{
			if ((*this)[i].l == (*this)[i].r)
			{
				(*this)[i].max += val;
				(*this)[i].min += val;
				(*this)[i].sum += val;
			}
			else if (loc_l <= (*this)[i].l && (*this)[i].r <= loc_r) (*this)[i].lazy += val;
			else
			{
				if (((*this)[i].l + (*this)[i].r) / 2 >= loc_l) _update(i * 2);
				if (((*this)[i].l + (*this)[i].r) / 2 < loc_r) _update(i * 2 + 1);
				pushup(i);
			}
		}
		void pushdown(int i)
		{
			(*this)[i].sum += (*this)[i].lazy * ((*this)[i].r - (*this)[i].l + 1);
			(*this)[i].max += (*this)[i].lazy;
			(*this)[i].min += (*this)[i].lazy;
			if ((*this)[i].l != (*this)[i].r)
			{
				(*this)[i * 2].lazy += (*this)[i].lazy;
				(*this)[i * 2 + 1].lazy += (*this)[i].lazy;
			}
			(*this)[i].lazy = 0;
		}
		long long _query(int i)
		{
			if ((*this)[i].lazy != 0) pushdown(i);
			if ((*this)[i].l == (*this)[i].r) return (*this)[i].sum;
			else if (loc <= ((*this)[i].l + (*this)[i].r) / 2) return _query(i * 2);
			else return _query(i * 2 + 1);
		}
};

区间查询

与区间修改类似,区间查询依然采用分块的思想,将要查询的区间分成线段树的若干个结点,再将这些结点的信息综合起来得到目标区间的信息。同时,区间查询与单点查询一样,都涉及到lazy标记的下传。
单点查询lazy标记的下传策略是:只要目标查询位置包含在当前的递推区间内,标记就下传。
区间查询lazy标记的下传策略是:只要目标查询区间与当前递推区间的交集不为空,标记就下传。
单点查询时,目标位置一定包含在递推区间内;区间查询时,目标区间与递推区间的交集一定不为空。
因此二者下传策略相同,都是在刚进入函数时就下传。
在这里插入图片描述
如图,依然以修改了[1, 9]的线段树为例,现在要查询a[3, 7]的信息。黄色结点是查询前信息被更新过的结点,蓝色结点是查询后信息被更新、标记被下传的结点,绿色结点是查询前后所有lazy标记不为0的结点。
我们回顾一下单点查询的代码:

long long query(int loc, int i)
{
	if (c[i].lazy != 0) pushdown(i);
	if (c[i].l == c[i].r) return c[i].sum;
	else if (loc <= (c[i].l + c[i].r) / 2) return query(i * 2);
	else return query(i * 2 + 1);
}

类比单点查询,区间查询的代码结构应该是这样的:

long long query(int l, int r, int i)
{
	if (c[i].lazy != 0) pushdown(i);//相同的下传策略
	//递归
}

那么递归部分的代码怎么写呢?在递归过程中,当前递归区间与目标查询区间之间有四种情况:

  1. 当前区间 ⊆ \subseteq 目标区间
    在这里插入图片描述
    这种情况下递归的返回值应该是当前区间的信息,
    如果查询的是区间和:
if (l <= c[i].l && c[i].r <= r) return c[i].sum;

如果查询的是区间最大值:

if (l <= c[i].l && c[i].r <= r) return c[i].max;

如果查询的是区间最小值:

if (l <= c[i].l && c[i].r <= r) return c[i].min;
  1. 当前区间 ∩ \cap 目标区间 ⊇ { m i d , m i d + 1 } \supseteq \{mid, mid + 1\} {mid,mid+1}
    在这里插入图片描述
    这种情况下递归的返回值应该是左右子区间的返回信息的并,
    如果查询的是区间和:
else if (l <= mid && r > mid) return query(l, r, i * 2) + query(l, r, i * 2 + 1);

如果查询的是区间最大值:

else if (l <= mid && r > mid) return max(query(l, r, i * 2), query(l, r, i * 2 + 1));

如果查询的是区间最小值:

else if (l <= mid && r > mid) return min(query(l, r, i * 2), query(l, r, i * 2 + 1));
  1. 当前区间 ∩ \cap 目标区间 ⊆ \subseteq 当前区间的左子区间
    在这里插入图片描述
    这种情况下递归的返回值应该是左子区间的返回值:
else if (r <= mid) return query(l, r, i * 2);
  1. 当前区间 ∩ \cap 目标区间 ⊆ \subseteq 当前区间的右子区间
    在这里插入图片描述
    这种情况下递归的返回值应该是右子区间的返回值:
else if (l > mid) return query(l, r, i * 2 + 1);

综上所述,区间查询的代码如下,
如果是区间和:

long long query(int l, int r, int i)
{
	int mid = (c[i].l + c[i].r) / 2;
	if (c[i].lazy != 0) pushdown(i);
	if (l <= c[i].l && r >= c[i].r) return c[i].sum;
	else if (l <= mid && r > mid) return query(l, r, i * 2) + query(l, r, i * 2 + 1);
	else if (r <= mid) return query(l, r, i * 2);
	else if (l > mid) return query(l, r, i * 2 + 1);
}

如果是区间最大值:

long long query_max(int l, int r, int i)
{
	int mid = (c[i].l + c[i].r) / 2;
	if (c[i].lazy != 0) pushdown(i);
	if (l <= c[i].l && r >= c[i].r) return c[i].max;
	else if (l <= mid && r > mid) return max(query_max(l, r, i * 2), query_max(l, r, i * 2 + 1));
	else if (r <= mid) return query_max(l, r, i * 2);
	else if (l > mid) return query_max(l, r, i * 2 + 1);
}

如果是区间最小值:

long long query_min(int l, int r, int i)
{
	int mid = (c[i].l + c[i].r) / 2;
	if (c[i].lazy != 0) pushdown(i);
	if (l <= c[i].l && r >= c[i].r) return c[i].min;
	else if (l <= mid && r > mid) return min(query_min(l, r, i * 2), query_min(l, r, i * 2 + 1));
	else if (r <= mid) return query_min(l, r, i * 2);
	else if (l > mid) return query_min(l, r, i * 2 + 1);
}

封装成类:

若编译失败,请使用最高版本的Visual Studio
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Node
{
	public:
		int l, r;
		long long sum, max, min, lazy;
		Node()
		{
			l = 0;
			r = 0;
			sum = 0;
			max = 0;
			min = 0;
			lazy = 0;
		}
};
class SegTree : vector<Node>
{
	public:
		int size;
		explicit SegTree(int size = 0)
		{
			this->size = size > 0 ? size : 0;
			assign(this->size * 4, Node());
			build(1, size, 1);
		}
		void update(int l, int r, long long val)//区间修改
		{
			if (l < 1 || r < l || r > size) return;
			loc_l = l;
			loc_r = r;
			this->val = val;
			_update(1);
		}
		void update(int loc, long long val)//重载一个单点修改
		{
			if (loc < 1 || loc > size) return;
			loc_l = loc_r = loc;
			this->val = val;
			_update(1);
		}
		long long query(int l, int r)//区间查询
		{
			if (l < 1 || r < l || r > size) return 0;
			loc_l = l;
			loc_r = r;
			return _query(1);
		}
		long long query(int loc)//重载一个单点查询
		{
			if (loc < 1 || loc > size) return 0;
			loc_l = loc_r = loc;
			return _query(1);
		}
		long long query_max(int l, int r)//区间最大值查询
		{
			if (l < 1 || r < l || r > size) return 0;
			loc_l = l;
			loc_r = r;
			return _query_max(1);
		}
		long long query_min(int l, int r)//区间最小值查询
		{
			if (l < 1 || r < l || r > size) return 0;
			loc_l = l;
			loc_r = r;
			return _query_min(1);
		}
		
	private:
		int loc_l, loc_r;
		long long val;
		void build(int l, int r, int loc)
		{
			(*this)[loc].l = l;
			(*this)[loc].r = r;
			if (l == r) return;
			build(l, (l + r) / 2, loc * 2);
			build((l + r) / 2 + 1, r, loc * 2 + 1);
		}
		void pushup(int i)
		{
			(*this)[i].max = max((*this)[i * 2].max + (*this)[i * 2].lazy, (*this)[i * 2 + 1].max + (*this)[i * 2 + 1].lazy);
			(*this)[i].min = min((*this)[i * 2].min + (*this)[i * 2].lazy, (*this)[i * 2 + 1].min + (*this)[i * 2 + 1].lazy);
			(*this)[i].sum = (*this)[i * 2].sum + (*this)[i * 2].lazy * ((*this)[i * 2].r - (*this)[i * 2].l + 1) + (*this)[i * 2 + 1].sum + (*this)[i * 2 + 1].lazy * ((*this)[i * 2 + 1].r - (*this)[i * 2 + 1].l + 1);
		}
		void _update(int i)
		{
			if ((*this)[i].l == (*this)[i].r)
			{
				(*this)[i].max += val;
				(*this)[i].min += val;
				(*this)[i].sum += val;
			}
			else if (loc_l <= (*this)[i].l && (*this)[i].r <= loc_r) (*this)[i].lazy += val;
			else
			{
				int mid = ((*this)[i].l + (*this)[i].r) / 2;
				if (mid >= loc_l) _update(i * 2);
				if (mid < loc_r) _update(i * 2 + 1);
				pushup(i);
			}
		}
		void pushdown(int i)
		{
			(*this)[i].sum += (*this)[i].lazy * ((*this)[i].r - (*this)[i].l + 1);
			(*this)[i].max += (*this)[i].lazy;
			(*this)[i].min += (*this)[i].lazy;
			if ((*this)[i].l != (*this)[i].r)
			{
				(*this)[i * 2].lazy += (*this)[i].lazy;
				(*this)[i * 2 + 1].lazy += (*this)[i].lazy;
			}
			(*this)[i].lazy = 0;
		}
		long long _query(int i)
		{
			int mid = ((*this)[i].l + (*this)[i].r) / 2;
			if ((*this)[i].lazy != 0) pushdown(i);
			if (loc_l <= (*this)[i].l && loc_r >= (*this)[i].r) return (*this)[i].sum;
			else if (loc_l <= mid && loc_r > mid) return _query(i * 2) + _query(i * 2 + 1);
			else if (loc_r <= mid) return _query(i * 2);
			else if (loc_l > mid) return _query(i * 2 + 1);
		}
		long long _query_max(int i)
		{
			int mid = ((*this)[i].l + (*this)[i].r) / 2;
			if ((*this)[i].lazy != 0) pushdown(i);
			if (loc_l <= (*this)[i].l && loc_r >= (*this)[i].r) return (*this)[i].max;
			else if (loc_l <= mid && loc_r > mid) return max(_query_max(i * 2), _query_max(i * 2 + 1));
			else if (loc_r <= mid) return _query_max(i * 2);
			else if (loc_l > mid) return _query_max(i * 2 + 1);
		}
		long long _query_min(int i)
		{
			int mid = ((*this)[i].l + (*this)[i].r) / 2;
			if ((*this)[i].lazy != 0) pushdown(i);
			if (loc_l <= (*this)[i].l && loc_r >= (*this)[i].r) return (*this)[i].min;
			else if (loc_l <= mid && loc_r > mid) return min(_query_min(i * 2), _query_min(i * 2 + 1));
			else if (loc_r <= mid) return _query_min(i * 2);
			else if (loc_l > mid) return _query_min(i * 2 + 1);
		}
};

线段树的空间优化

未完待续…

多维线段树(以二维线段树为例)

未完待续…

线段树的应用与习题

未完待续…

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值