题目大意

给定一个数列,有如下操作:

  • 将区间 $[l,r]$ 上的数乘上 $x$
  • 将区间 $[l,r]$ 上的数加上 $x$
  • 询问区间 $[l,r]$ 上的数之和

分析

很经典的线段树教学题,写完后对线段树的理解确实更深了。

从一次函数的角度考虑问题感觉更清晰。我们实际上在对每个序列上的值做一次函数

$$ f_i(x) = k_i x + m_i $$

先来看本题线段树的结构

template <class T>
struct SegmentTree {
	vector<Seg> tr;
	void pushup(int p);
	void pushdown(int p);
	void build(int l, int r, int p = 1);
	void modify(int l, int r, T k, T m, int p = 1);
	T query(int l, int r, int p = 1);
};

懒标记:在知道一段当前值的情况下,可以立刻知道该段被操作后的值,不必重新计算所有的数。因此可以把当前的操作存起来,询问的时候再对子节点做真实操作。

真实操作也称永久化,操作后懒标记就被下放子节点。懒标记意味着当前节点已经完成了永久化,子节点没有完成,故询问子节点值前需要更新。

  • pushup:当 $p$ 的子节点被改变时,需要用此方法对节点 $p$ 的值进行更新。
  • pushdown:当询问 $p$ 的子节点的值时,如果 $p$ 节点有懒标记,则下放到 $p$ 的子节点。
  • modify:对所有在 $[l,r]$ 区间内的节点做永久化,并打上懒标记。
  • query:询问在 $[l,r]$ 区间内的节点。

当子节点懒标记上已经有值时,下放标记需要合并,即是函数复合。

$$ f_i(f_j(x)) = a_i(a_j x + b_j) + b_i = a_ia_j x + a_i b_j + b_i $$

附:代码展开