SGT 进阶(?
动态开点
当正常堆式建树开不下时(
例题
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
区间最大子段和。
这个问题就是前文所述的典型的封闭性问题。
如果我们考虑答案纯粹从左子树或右子树转移而来的话,那么答案是沿树链严格递减的。因为我们忽略了最大子段和横跨左右区间的情况。
考虑维护最大前缀和、最大后缀和与最大子段和的标记,那么当前节点的最大子段和除了继承左右子树答案以外,还可以通过左区间的最大后缀和与右区间的最大前缀和转移而来。
那么又该如何去更新最大前缀和与最大后缀和的标记呢?首先还是一定可以继承。还有一种情况(以最大前缀和为例):若左区间的最大前缀和为左区间区间和,那么便可以继续拼接右区间的最大前缀和进行更新。最大后缀和亦然。
设 ans
pre
suf
sum
为最大子段和、最大前缀和、最大后缀和与区间和,那么 psup
函数应为:
inline void psup(int p) {
t[p].sum = t[ls(p)].sum + t[rs(p)].sum;
t[p].pre = max(t[ls(p)].pre, t[ls(p)].sum + t[rs(p)].pre);
t[p].suf = max(t[rs(p)].suf, t[rs(p)].sum + t[ls(p)].suf);
t[p].ans = max({t[ls(p)].ans, t[rs(p)].ans, t[ls(p)].suf + t[rs(p)].pre});
return ;
}
值得一提的是像这样标记特别多的时候用 struct
会非常舒服。
第二个问题是我们只维护了一段线段树可以管辖到的区间的信息,若要查询应该怎么办。
考虑到线段树优秀的分治性质,我们分三段讨论。
- 询问区间包含当前访问的管辖区间
直接返回管辖区间的信息即可。
- 询问区间在当前访问的管辖区间中点左侧
即
- 询问区间在当前访问的管辖区间中点右侧
即
- 询问区间横跨当前访问的管辖区间中点
先递归解决当前访问的管辖区间左右侧部分的问题,之后用类似 psup
的方法合并信息即可。
query
函数:
inline Node work(Node x, Node y) {
Node res;
res.sum = x.sum + y.sum;
res.pre = max(x.pre, x.sum + y.pre);
res.suf = max(y.suf, y.sum + x.suf);
res.ans = max({x.ans, y.ans, x.suf + y.pre});
return res;
}
inline Node query(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return t[p];
if(l > mid) return query(l, r, rson);
if(r <= mid) return query(l, r, lson);
Node lres = query(l, r, lson), rres = query(l, r, rson);
return work(lres, rres);
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!