..
第六章 线段树
线段树的建立与操作
例1:P3372 【模板】线段树 1
在这里需要注意的是,使用线段树所维护的信息必须具有可合并性。
线段树的应用
例2:P3870 [TJOI2009] 开关
五倍经验
- P2846
- SP7259
- P2574
- P5057
例3:P1438 无聊的数列
考虑等差数列有两个要素:首项 $x$、公差 $y$。只要这两项确定了,等差数列就唯一确定了。
考虑这两个要素也具有 “可加性”:将首项分别为 $x_1$、$x_2$,公差分别为 $y_1$、$y_2$ 的两个等差数列的每一项对应相加,得到的数列也是一个等差数列,且它的首项为 $x_1 + x_2$,公差为 $y_1 + y_2$。
于是使用两个延迟标记,分别表示首相和公差即可。
void Modify(int xNode, int l, int r, long long K, long long D) {
if (l <= node[xNode].l && node[xNode].r <= r) {
node[xNode].tag = true;
node[xNode].K += K + (node[xNode].l - l) * D;
node[xNode].D += D;
return;
}
if (node[xNode].tag) PushDown(xNode);
if (l <= node[node[xNode].lNode].r) Modify(node[xNode].lNode, l, r, K, D);
if (r >= node[node[xNode].rNode].l) Modify(node[xNode].rNode, l, r, K, D);
return;
}
long long Query(int xNode, int p) {
if (node[xNode].l == node[xNode].r) return node[xNode].val + node[xNode].K;
if (node[xNode].tag) PushDown(xNode);
long long ret = 0;
if (p <= node[node[xNode].lNode].r) ret += Query(node[xNode].lNode, p);
if (p >= node[node[xNode].rNode].l) ret += Query(node[xNode].rNode, p);
ret += node[xNode].K;
ret += node[xNode].D * (p - node[xNode].l);
return ret;
}
例4:P1253 [yLOI2018] 扶苏的问题
需要注意的是,因为可能赋值为 $0$ 或负数,所以需要用一个不可能出现的数来表示不存在。
例5:P3373 【模板】线段树 2
标记的下传顺序十分关键:在 $\text{pushdown}$ 时,必须先下传乘法标记,再下传加法标记。因为乘法标记在下传时,需要让子节点的加法标记也乘上当前节点的乘法标记值,如果先下传加法标记,会让下传的那部分加法标记再乘上乘法标记,而事实上这部分标记已经在原节点乘过了,因此会计算错误。
例6:P4513 小白逛公园
最大字段和。
void PushUp(Node &xNode, Node lNode, Node rNode) {
xNode.res = std::max(lNode.res, rNode.res);
xNode.res = std::max(lNode.suf + rNode.pre, xNode.res);
xNode.pre = std::max(lNode.val + rNode.pre, lNode.pre);
xNode.suf = std::max(lNode.suf + rNode.val, rNode.suf);
xNode.val = lNode.val + rNode.val;
return;
}
Node Query(int xNode, int a, int b) {
if (a <= node[xNode].l && node[xNode].r <= b) return node[xNode];
if (b <= node[node[xNode].lNode].r) return Query(node[xNode].lNode, a, b);
if (a >= node[node[xNode].rNode].l) return Query(node[xNode].rNode, a, b);
Node ret;
Node lNode = Query(node[xNode].lNode, a, b);
Node rNode = Query(node[xNode].rNode, a, b);
PushUp(ret, lNode, rNode);
return ret;
}
双倍经验
- SP1716
权值线段树
对一个数列 $a$ 构造一个数列 $b$,其中 $b_i$ 表示 $a$ 中数值 $i$ 出现的次数,也即 $a_j = i$ 的 $j$ 的个数,这样的数列 $b$ 称作数列 $a$ 的权值数列,对 $b$ 构造的线段树成为 $a$ 的权值线段树。
权值线段树在很多计数问题中有着重要的应用,因为它可以轻松地修改对 $a$ 维护的统计信息。容易发现,当将 $a_i$ 从 $x$ 修改为 $y$ 时,只需要对其权值线段树做两次单点修改,即给 $b_x$ 减去 $1$,给 $b_y$ 加上 $1$。