SGT 进阶(?
动态开点
当正常堆式建树开不下时(\(n\) 或 \(V\) 过大),通常使用动态开点。
例题
P2781 传教
算是很板了吧?
每次修改的时候,若当前访问节点未建立则新建节点并回溯至上一个节点记录左右儿子。实测写 &
引用或 struct
是很方便的。
要十分注意的是在 work
函数(单点修改 & 标记下放)里面一定要判断当前访问节点是否建立!
SGT namespace
代码:
namespace SGT {
#define mid (L + R) >> 1
#define son p, L, R
#define lson ls[p], L, (L + R) >> 1
#define rson rs[p], ((L + R) >> 1) + 1, R
int tot, ls[N], rs[N], tag[N], sum[N];
inline void psup(int p) {sum[p] = sum[ls[p]] + sum[rs[p]];}
inline void work(int &p, int L, int R, int k) {
if(!p) p = ++ tot;
tag[p] += k;
sum[p] += k * (R - L + 1);
}
inline void psd(int p, int L, int R) {
work(lson, tag[p]), work(rson, tag[p]);
tag[p] = 0;
}
inline void add(int l, int r, int k, int &p, int L = 1, int R = n) {
if(! p) p = ++ tot;
if(l <= L && R <= r) {
work(son, k);
return;
}
psd(son);
if(l <= mid) add(l, r, k, lson);
if(r > mid) add(l, r, k, rson);
psup(p);
}
inline int query(int l, int r, int &p, int L = 1, int R = n) {
int res = 0;
if(! p) return 0;
if(l <= L && R <= r) return sum[p];
psd(son);
if(l <= mid) res += query(l, r, lson);
if(r > mid) res += query(l, r, rson);
return res;
}
}
标记设计
用线段树解决问题,首先得考虑维护哪些信息。若不带修,任何满足结合律且封闭的信息(称为半群)都是可维护的。结合律一般都有,封闭性帮助我们设计信息。
—— Alex_Wei
通俗地讲,如果当前维护的信息是满足线段树的分治结构的,一般是可以维护的。
一般以最大子段和为典型代表。
例题
SP1716 GSS3 - Can you answer these queries III
单点修改,区间最大子段和。
这个问题就是前文所述的典型的封闭性问题。
如果我们考虑答案纯粹从左子树或右子树转移而来的话,那么答案是沿树链严格递减的。因为我们忽略了最大子段和横跨左右区间的情况。
考虑维护最大前缀和、最大后缀和与最大子段和的标记,那么当前节点的最大子段和除了继承左右子树答案以外,还可以通过左区间的最大后缀和与右区间的最大前缀和转移而来。
那么又该如何去更新最大前缀和与最大后缀和的标记呢?首先还是一定可以继承。还有一种情况(以最大前缀和为例):若左区间的最大前缀和为左区间区间和,那么便可以继续拼接右区间的最大前缀和进行更新。最大后缀和亦然。
设 mx
lmx
rmx
sum
为最大子段和、最大前缀和、最大后缀和与区间和,那么 psup
函数应为:
值得一提的是像这样标记特别多的时候用 struct
会非常舒服。