数据结构专题-专项训练:线段树1

回顾:

上一篇博文 数据结构专题-学习笔记:线段树 中,我们学会了线段树的基础操作。那么接下来,让我们看看线段树又能玩出什么花样。

线段树作为一种数据结构,其实题目都有一定的套路性:

  1. 我们需要维护什么?\(sum\) ,还是 \(max,min\) ?或是别的一些奇怪的东西?
  2. 线段树的每个叶子节点是什么? 是数字,是另外一棵线段树(树套树)(当然这里不讲)还是些别的?
  3. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?
  4. 要不要重载运算符?(有些题目使用重载运算符会简便的多)
  5. 最后又要怎么修改?怎么查询?

明白这 5 点,剩下的操作就是打代码。

那么就开始吧!

3.例题

题单:


P1438 无聊的数列

题目有两个操作:区间加等差数列,单点询问。

我们对五个问题考虑一遍:

  1. 我们需要维护什么?

对于这个问题,我们发现直接维护显然是维护不了的,因此需要考虑一些别的东西。

想想等差数列的性质:相邻两个数之差相等。?相邻两个数之差相等?这难道不是 差分 干的事情吗?

于是这道题的第一个问题就确定了:我们维护一个差分数组 \(sum\) ,这样就将区间加等差数列操作变成了区间加操作。

这样不就是 『区间加』+『区间查询』 的模板了吗?(为什么是区间查询?见第 5 问)

但是维护差分数组 \(sum\) 的时候,有一些细节需要注意:

  1. \(sum_l\) 要加上首项。这个操作可以看成对 \([l,l]\) 的区间修改(虽然 \([l,l]\) 不符合书写规范,但是这个在代码里面是没有问题的,此时 \(l(p) >= l \&\& r(p) <= r\) 等价于 \(l==r\))。
  2. \(sum_{l+1,...,r}\) 要统一加上 \(d\)
  3. 不要忘记在 \(sum_{r+1}\) 上减去 \(首项+d\times(r-l+1)\)
  4. 不要忘记开 \(\text{long long}\)!!!!!。

  1. 线段树的每个叶子节点是什么?

根据我们上面的讨论,初始我们针对 \(sum\) 建树(下文称『在 \(sum\) 上建树』),\(sum\) 初始化为 0,每个叶子节点代表的是 \(sum\) 的值。


  1. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?

因为有区间加操作,于是我们需要维护加法 lazy_tag :\(add\)


  1. 要不要重载运算符?

显然不要。


  1. 最后又要怎么修改?怎么查询?

修改上面已经讲了。

因为我们这里变成了差分数组,所以我们最后的答案应该是(假设查询 \(a_x\)):$$a_x+\sum_{i=1}^{x}sum_i$$

而后面的求和就是『区间查询』的基本操作。


代码:

#include <bits/stdc++.h>

typedef long long LL;
const int MAXN = 1e5 + 10;
int n, m, a[MAXN];
struct node
{
	int l, r;
	LL add, sum;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define a(p) tree[p].add
	#define s(p) tree[p].sum
}tree[MAXN << 2];

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}

void build(int p, int l, int r)
{
	l(p) = l, r(p) = r;
	if (l == r) {s(p) = 0; return ;}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
	s(p) = s(p << 1) + s(p << 1 | 1);
}//对 sum 建树,没有定义 sum 的原因是我们不需要 sum 这个数组出现

void spread(int p)
{
	if (a(p))
	{
		s(p << 1) += a(p) * (r(p << 1) - l(p << 1) + 1);
		s(p << 1 | 1) += a(p) * (r(p << 1 | 1) - l(p << 1 | 1) + 1);
		a(p << 1) += a(p); a(p << 1 | 1) += a(p); a(p) = 0;
	}
}

void add(int p, int l, int r, LL k)
{
	if (l(p) >= l && r(p) <= r) {s(p) += k * (r(p) - l(p) + 1); a(p) += k; return ;}
	spread(p);
	int mid = (l(p) + r(p)) >> 1;
	if (l <= mid) add(p << 1, l, r, k);
	if (r > mid) add(p << 1 | 1, l, r, k);
	s(p) = s(p << 1) + s(p << 1 | 1);
}

LL ask(int p, int l, int r)
{
	if(l(p) >= l && r(p) <= r) return s(p);
	spread(p);
	int mid = (l(p) + r(p)) >> 1;LL val = 0;
	if (l <= mid) val += ask(p << 1, l, r);
	if (r > mid) val += ask(p << 1 | 1, l, r);
	return val;
}

int main()
{
	n = read(); m = read();
	for (int i = 1; i <= n; ++i) a[i] = read();
	build(1, 1, n);
	for (int i = 1; i <= m; ++i)
	{
		int opt = read();
		if (opt == 1)
		{
			int l = read(), r = read(), k = read(), d = read();
			add(1, l, l, k);//加上首项
			if (r > l) add(1, l + 1, r, d);//加上公差
			if (r != n) add(1, r + 1, r + 1, -((LL)k + (r - l) * d));//减去总和
		}
		else
		{
			int p = read();
			printf("%lld\n", ask(1, 1, p) + a[p]);
		}
	}
	return 0;
}

P4145 上帝造题的七分钟2 / 花神游历各国

这道题分块可以水过,但是我们看看线段树需要怎么做。


  1. 我们需要维护什么?

显然要维护和,但是怎么修改呢?

首先按按计算器:$$\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{10^{12}}}}}}} \approx 1$$

于是我们发现,每个数至多做 6 次开根号操作就变成了 1。

那么因此我们可以维护这样一个东西:区间最大值。

为什么要维护它呢?你想啊,如果我们知道这一段的区间最大值,那么如果区间最大值为 1 我们不是就可以直接跳过这一段区间而不进行操作了吗?

而如果最大值不是 1,那么暴力修改即可。

这样看起来好像非常暴力,但是实际上修改操作的时间复杂度至多为 \(O(6nlogn)\),而且在 6 次操作完以后就不会再进行区间修改操作,最后就变成了 \(O(mlogn)\) 的时间复杂度。


  1. 线段树的每个叶子节点是什么?

就是每一个数。


  1. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?

不需要 lazy_tag,因为我们是暴力修改。


  1. 要不要重载运算符?

显然不要。


  1. 最后又要怎么修改?怎么查询?

修改上面已经讲了。

经典区间查询,直接找即可。


代码:

#include <bits/stdc++.h>
#define Max(a, b) ((a > b) ? a : b)
using namespace std;

const int MAXN = 1e5 + 10;
typedef long long LL;
int n, m;
LL a[MAXN];
struct node
{
	int l, r;
	LL sum, maxn;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define s(p) tree[p].sum
	#define m(p) tree[p].maxn
}tree[MAXN << 2];

LL read()
{
	LL sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}

void build(int p, int l, int r)
{
	l(p) = l, r(p) = r;
	if(l == r) {s(p) = m(p) = a[l]; return ;}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
	s(p) = s(p << 1) + s(p << 1 | 1);
	m(p) = Max(m(p << 1), m(p << 1 | 1));
}

void change(int p, int l, int r)
{
	if (l(p) == r(p)) {s(p) = sqrt(s(p)); m(p) = sqrt(m(p)); return ;}//找到叶子节点暴力修改
	if (m(p) == 1) return ;//最大值为 1 则直接跳过
	int mid = (l(p) + r(p)) >> 1;
	if (l <= mid) change(p << 1, l, r);
	if (r > mid) change(p << 1 | 1, l, r);
	s(p) = s(p << 1) + s(p << 1 | 1);
	m(p) = Max(m(p << 1), m(p << 1 | 1));
}

LL ask(int p, int l, int r)
{
	if (l(p) >= l && r(p) <= r) return s(p);
	int mid = (l(p) + r(p)) >> 1; LL val = 0;
	if (l <= mid) val += ask(p << 1, l, r);
	if (r > mid) val += ask(p << 1 | 1, l, r);
	return val;
}

int main()
{
	n = read();
	for (int i = 1; i <= n; ++i) a[i] = read();
	build(1, 1, n);
	m = read();
	for (int i = 1; i <= m; ++i)
	{
		int opt = read(), l = read(), r = read();
		if (l > r) swap(l, r);
		if (opt == 0) change(1, l, r);
		else printf("%lld\n", ask(1, l, r));
	}
	return 0;
}

P2073 送花

  1. 先说点闲话

这道题的题面太坑了。。。。。。 3 操作与 2 操作在题面中的序号竟然是颠倒的??????我一开始没看到,导致 WA 了很久。


  1. 我们需要维护什么?

这道题其实还是比较显然的吧。

我们需要维护美丽值总和,价格总和,价格最大值,价格最小值。


  1. 线段树的每个叶子节点是什么?

一开始建树的时候我们需要将其初始化为 0,然后过程中我们可以将加入/删除花束看成单点修改的操作(作者因为太懒写的是区间修改)。


  1. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?

单点修改当然不需要 lazy_tag。


  1. 要不要重载运算符?

显然不用。


  1. 最后又要怎么修改?怎么查询?

对于加入花束的操作,我们假设新加入的编号为 \(n\) ,那么执行一次对 \(n\) 的单点修改即可。

对于删除花束的操作,我们直接模仿二叉树左右儿子搜一搜就可以了。

查询?直接输出根节点的维护信息不就好了?


代码:

#include <bits/stdc++.h>
#define Max(a, b) ((a > b) ? a : b)
#define Min(a, b) ((a < b) ? a : b)
using namespace std;

const int MAXN = 1e5 + 10;
typedef long long LL;
int n;
struct node
{
	int l, r, maxn, minn;
	LL sumw, sumc;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define maxn(p) tree[p].maxn
	#define minn(p) tree[p].minn
	#define sumw(p) tree[p].sumw
	#define sumc(p) tree[p].sumc
}tree[MAXN << 2];
bool book[(MAXN << 3) + (MAXN << 1)];

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}

void build(int p, int l, int r)
{
	l(p) = l, r(p) = r;
	if (l == r) {sumw(p) = sumc(p) = maxn(p) = 0; minn(p) = 0x7f7f7f7f; return ;}
	int mid = (l(p) + r(p)) >> 1;
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
	sumw(p) = sumc(p) = maxn(p) = 0; minn(p) = 0x7f7f7f7f;
}

void change(int p, int l, int r, int w, int c)
{
	if (book[c]) return ;
	if (l(p) >= l && r(p) <= r)
	{
		sumw(p) += w;
		sumc(p) += c;
		maxn(p) = Max(maxn(p), c);
		minn(p) = Min(minn(p), c);
		book[c] = 1;
		return ;
	}
	int mid = (l(p) + r(p)) >> 1;
	if (l <= mid) change(p << 1, l, r, w, c);
	if (r > mid) change(p << 1 | 1, l, r, w, c);
	sumw(p) = sumw(p << 1) + sumw(p << 1 | 1);
	sumc(p) = sumc(p << 1) + sumc(p << 1 | 1);
	maxn(p) = Max(maxn(p << 1), maxn(p << 1 | 1));
	minn(p) = Min(minn(p << 1), minn(p << 1 | 1));
}

void Delete_the_cheapest(int p)
{
	if (l(p) == r(p))
	{
		book[sumc(p)] = 0;
		sumw(p) = sumc(p) = 0;
		maxn(p) = 0; minn(p) = 0x7f7f7f7f;
		return ;
	}
	if (minn(p) == 0x7f7f7f7f) return ;
	if (minn(p << 1) == minn(p)) Delete_the_cheapest(p << 1);
	else Delete_the_cheapest(p << 1 | 1);
	sumw(p) = sumw(p << 1) + sumw(p << 1 | 1);
	sumc(p) = sumc(p << 1) + sumc(p << 1 | 1);
	maxn(p) = Max(maxn(p << 1), maxn(p << 1 | 1));
	minn(p) = Min(minn(p << 1), minn(p << 1 | 1));
}

void Delete_the_most_expensive(int p)
{
	if (l(p) == r(p))
	{
		book[sumc(p)] = 0;
		sumw(p) = sumc(p) = 0;
		maxn(p) = 0; minn(p) = 0x7f7f7f7f;
		return ;
	}
	if (maxn(p) == 0) return ;
	if (maxn(p << 1) == maxn(p)) Delete_the_most_expensive(p << 1);
	else Delete_the_most_expensive(p << 1 | 1);
	sumw(p) = sumw(p << 1) + sumw(p << 1 | 1);
	sumc(p) = sumc(p << 1) + sumc(p << 1 | 1);
	maxn(p) = Max(maxn(p << 1), maxn(p << 1 | 1));
	minn(p) = Min(minn(p << 1), minn(p << 1 | 1));
}

int main()
{
	build(1, 1, 100000);
	while (1)
	{
		int opt = read();
		if (opt == -1) break;
		if (opt == 1)
		{
			int w = read(), c = read();
			++n; change(1, n, n, w, c);
		}
		if (opt == 3) Delete_the_cheapest(1);
		if (opt == 2) Delete_the_most_expensive(1);
	}
	printf("%lld %lld\n", tree[1].sumw, tree[1].sumc);
	return 0;
}

P6327 区间加区间sin和

这道题原先是 Ynoi 的,但是后面被除名了。

  1. 我们需要维护什么?

我们先看看目前数学课本上的两个公式:

\[\sin(\alpha+\beta) = \sin(\alpha) \times \cos(\beta)+\cos(\alpha) \times \sin(\beta) \]

\[\cos(\alpha+\beta) = \cos(\alpha) \times \cos(\beta) - \sin(\alpha) \times \sin(\beta) \]

所以呢?我们维护 \(\sin,\cos\) 两个东西,然后更新的时候按照上面的公式以区间加的形式更新即可。

为什么可以整体更新?考虑结合律。


  1. 线段树的每个叶子节点是什么?

初始值,及其 \(\sin,\cos\) 值。


  1. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?

区间加操作需要 lazy_tag。


  1. 要不要重载运算符?

不需要。


  1. 最后又要怎么修改?怎么查询?

修改直接区间修改即可。

查询直接区间查询即可。


代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int MAXN = 2e5 + 10;
int n, a[MAXN], m;
struct node
{
	int l, r;
	LL add;
	double Sin, Cos;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define s(p) tree[p].Sin
	#define c(p) tree[p].Cos
	#define a(p) tree[p].add
}tree[MAXN << 2];

int read()
{
	int sum = 0; char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum;
}

void build(int p, int l, int r)
{
	l(p) = l, r(p) = r;
	if (l == r) {s(p) = sin(a[l]), c(p) = cos(a[l]); return ;}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
	s(p) = s(p << 1) + s(p << 1 | 1);
	c(p) = c(p << 1) + c(p << 1 | 1);
}

void spread(int p)
{
	if (a(p))
	{
		double s = s(p << 1), c = c(p << 1);//不要忘记提前存下来!
		s(p << 1) = s * cos(a(p)) + c * sin(a(p));
		c(p << 1) = c * cos(a(p)) - s * sin(a(p));
		s = s(p << 1 | 1), c = c(p << 1 | 1);
		s(p << 1 | 1) = s * cos(a(p)) + c * sin(a(p));
		c(p << 1 | 1) = c * cos(a(p)) - s * sin(a(p));
		a(p << 1) += a(p), a(p << 1 | 1) += a(p); a(p) = 0;
	}
}

void change(int p, int l, int r, int val)
{
	if (l(p) >= l && r(p) <= r)
	{
		double s = s(p), c = c(p);
		s(p) = s * cos(val) + c * sin(val);
		c(p) = c * cos(val) - s * sin(val);
		a(p) += val; return ;
	}
	spread(p);
	int mid = (l(p) + r(p)) >> 1;
	if (l <= mid) change(p << 1, l, r, val);
	if (r > mid) change(p << 1 | 1, l, r, val);
	s(p) = s(p << 1) + s(p << 1 | 1);
	c(p) = c(p << 1) + c(p << 1 | 1);
}

double ask(int p, int l, int r)
{
	if (l(p) >= l && r(p) <= r) return s(p);
	spread(p);
	int mid = (l(p) + r(p)) >> 1; double val = 0;
	if (l <= mid) val += ask(p << 1, l, r);
	if (r > mid) val += ask(p << 1 | 1, l, r);
	return val;
}

int main()
{
	n = read();
	for (int i = 1; i <= n; ++i) a[i] = read();
	build(1, 1, n);
	m = read();
	for (int i = 1; i <= m; ++i)
	{
		int opt = read();
		if (opt == 1)
		{
			int l = read(), r = read(), val = read();
			change(1, l, r, val);
		}
		else
		{
			int l = read(), r = read();
			printf("%.1lf\n", ask(1, l, r));
		}
	}
	return 0;
}

U146450 奶茶分配

这道题是道好题目,很考验各位的算法功底与思维能力。

P.S. 题目因为一些原因隐藏了。

update:据说这玩意有点像 DDP 是吧(


  1. 我们需要维护什么?
  2. 线段树的每个叶子节点是什么?

  1. 最后又要怎么修改?怎么查询?

这个三个问题其实就很不好想了。

我们想一想,\(2 \leq n \leq 5,m \leq 100000\),其实通过这个数据范围我们不难发现,我们需要在操作上建树,线段树需要维护的是关于 \(n\) 的一些东西。

那么 \(n\) 这么小,我们肯定可以相对暴力一点的维护 \(n\)

于是这里有一个思路:矩阵。

我们将操作矩阵化。

首先我们看看矩阵里面的一种特殊情形:单位矩阵。比如:

\(\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix}\)

那么如果我们用这个矩阵跟前面的单位矩阵相乘:

\(\begin{bmatrix}a_1&a_2&a_3\end{bmatrix}\)

那么结果矩阵恰好就是:

\(\begin{bmatrix}a_1&a_2&a_3\end{bmatrix}\)

因此,我们可以针对 \(m\) 个操作建树,一开始所有叶子节点都是单位矩阵。

然后呢?对于修改操作:

比如我们现在要将 1 的奶茶分配给 2,3,那么矩阵就变成了这样(当然实际题目要求 \(\dfrac{1}{2}\)\(993244853\) 意义下的逆元):

\(\begin{bmatrix}0&\dfrac{1}{2}&\dfrac{1}{2}\\0&1&0\\0&0&1\end{bmatrix}\)

然后执行单点修改操作即可。

那么答案是什么呢?

这里线段树需要维护矩阵乘法的结果,最后取出根节点的矩阵与原始矩阵(\(a_i\) 构成的矩阵)相乘即可。

为什么是相乘?这与对矩阵的理解有关。

实际上如果你看过这篇博文(From myan)就会发现矩阵实质上是一种对坐标系的描述,而矩阵乘法 \(A \times B=C\) 实质上可以看成向量组 B 在坐标系 A 中描述的结果是向量组 C。

因此如果我们有两个已经操作过的矩阵 \(A,B\),现在要合并它们,那么我们只需要将 \(B\)\(A\) 中表示出来即可。

那么如何撤销修改呢?考虑到矩阵乘法满足结合律,因此撤销哪次操作就将其修改为单位矩阵即可。


  1. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?

单点修改不需要 lazy_tag。


  1. 要不要重载运算符?

这里需要!我们需要在结构体当中重载运算符 \(*\) ,来方便我们进行矩阵乘法。

代码:

#include <bits/stdc++.h>

using std::queue;

typedef long long LL;
const int MAXM = 1e5 + 10, P = 993244853;
int n, m, cnt, inv[10];
queue<int>q[10];
struct node
{
	int d[10][10];
	int x, y;
	
	node()
	{
		memset(d, 0, sizeof(d));
		x = y = 0;
	}
	
	void init(int xx, int yy)
	{
		memset(d, 0, sizeof(d));
		x = xx, y = yy;
		for (int i = 1; i <= x; ++i) d[i][i] = 1;
		return ;
	}//初始化单位矩阵
	
	node operator*(const node &b)
	{
		node c;
		c.x = x; c.y = b.y;
		for (int i = 1; i <= x; ++i)
			for (int k = 1; k <= y; ++k)
				for (int j = 1; j <= b.y; ++j)
					c.d[i][j] = (1ll * c.d[i][j] + 1ll * d[i][k] * b.d[k][j] % P) % P;
		return c;
	}//重载运算符
	
	void output()
	{
		for (int i = 1; i <= x; ++i)
		{
			for (int j = 1; j <= y; ++j) printf("%d ", d[i][j]);
			printf("\n");
		}
		return ;
	}//输出矩阵
}tree[MAXM << 2], a;

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') fh = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum * fh;
}

void getinv(int x)
{
	inv[1] = 1;
	for(int i = 2; i <= x; ++i) inv[i] = ((LL)P - (LL)P / i) * inv[P % i] % P;
}//求逆元

void build(int p, int l, int r)
{
	if (l == r) {tree[p].init(n, n); return ;}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
	tree[p].init(n, n);
}

void change(int p, int l, int r, int zzh, const node &s)
{
	if (l == r) {tree[p] = s; return ;}
	int mid = (l + r) >> 1;
	if (zzh <= mid) change(p << 1, l, mid, zzh, s);
	else change(p << 1 | 1, mid + 1, r, zzh, s);
	tree[p] = tree[p << 1] * tree[p << 1 | 1];
}

int main()
{
	n = read(); m = read(); a.x = 1; a.y = n;
	for (int i = 1; i <= n; ++i)  a.d[1][i]= read();
	build(1, 1, m); getinv(n);
	for (int i = 1; i <= m; ++i)
	{
		int opt = read();node t;
		if (opt == 1)
		{
			++cnt;
			int p = read(), k = read();
			t.init(n,n);
			t.d[p][p] = 0;
			for (int j = 1; j <= k; ++j)
			{
				int tmp = read(); t.d[p][tmp] = inv[k];
			}
			change(1, 1, m, cnt, t);
			q[p].push(cnt);
		}
		else
		{
			int p = read();
			if (!q[p].empty())
			{
				t.init(n, n);
				change(1, 1, m, q[p].front(), t);
				q[p].pop();
			}
		}
		node ans = a * tree[1];
		ans.output();
	}
	return 0;
}

4.小总结

对于线段树中到底要维护的是什么,我们主要考虑以下这 5 个问题:

  1. 我们需要维护什么?
  2. 线段树的每个叶子节点是什么?
  3. 需要 lazy_tag 吗?lazy_tag 又要维护什么呢?
  4. 要不要重载运算符?
  5. 最后又要怎么修改?怎么查询?

想清楚这 5 个问题,线段树的题目就不难做了。

那么接下来,我们看看 GSS1-5 的题目用线段树如何解决吧!(本质上还是这 5 步,只不过玩法不一样了,更具思维性)

详情请见 数据结构专题-专项训练:线段树2(GSS1-5)

posted @ 2022-04-13 21:43  Plozia  阅读(112)  评论(0编辑  收藏  举报