就拿洛谷的模板题来举例吧。
我们先简化一下各个操作的定义:
操作 1 1 1:区间加 k k k;
操作 2 2 2:区间同 v v v 取 min \min min;
操作 3 3 3:区间求和;
操作 4 4 4:区间求最大值;
操作 5 5 5:区间求最大的历史最大值。
其中操作 2 2 2 和 5 5 5 是比较复杂的,所以我们需要用到吉老师发明的线段树—— S e g m e n t T r e e B e a t s Segment~Tree~Beats Segment Tree Beats。
(注:所有的操作通过势能分析法可得总时间复杂度为 O ( m l o g 2 n ) O(m log_2 n) O(mlog2n),具体证明可以参考原论文)
(显然是笔者太菜了不会)
在分析操作之前,我们定义一些变量(指线段树节点上储存的变量):
(强烈建议拿草稿纸记录一下)
s u m a suma suma —— 区间内 A i A_i Ai 的和;
m x a mxa mxa —— 区间内 A i A_i Ai 的最大值;
c n t m x cntmx cntmx —— 区间内 A i A_i Ai 最大值的出现次数(方便求和时最大值与其它值分别计算);
c m x a cmxa cmxa —— 区间内 A i A_i Ai 的严格次大值(这个在操作 2 2 2 中会用到,实质是为了维护 c n t m x cntmx cntmx);
m x b mxb mxb —— 区间内 B i B_i Bi 的最大值;
然后再定义一些标记(同上):
t a g a m x tagamx tagamx —— 区间内 A i A_i Ai 的最大值的加法标记;
t a g a n m x taganmx taganmx —— 区间内非 A i A_i Ai 最大值的加法标记;
t a g b m x tagbmx tagbmx —— 注意这并不是区间内 B i B_i Bi 的最大值的加法标记,这是区间的 t a g a m x tagamx tagamx 的历史最大值(历史指上一次 P u s h d o w n Pushdown Pushdown 之后);
t a g b n m x tagbnmx tagbnmx —— 同上,这是区间的 t a g a n m x taganmx taganmx 的历史最大值。
为什么 t a g b m x tagbmx tagbmx 是 t a g a m x tagamx tagamx 的历史最大值?
比如有一个变量 v a l val val,在下一次 P u s h d o w n Pushdown Pushdown 前进行了两次加法操作:一次加 5 5 5,一次减 2 2 2,那么 t a g a m x tagamx tagamx 的历史最大值应当为 5 5 5,此时 v a l val val 才更有可能成为历史最大值。
然后我们来分析一下线段树的 P u s h u p , U p d a t e , P u s h d o w n Pushup,Update,Pushdown Pushup,Update,Pushdown 三个操作。
首先是 P u s h u p Pushup Pushup,大力维护即可。
代码:
inline void Pushup(int now)
{
suma[now] = suma[ls] + suma[rs];
mxa[now] = max(mxa[ls], mxa[rs]);
mxb[now] = max(mxb[ls], mxb[rs]);
if(mxa[ls] == mxa[rs])
{
cntmx[now] = cntmx[ls] + cntmx[rs];
cmxa[now] = max(cmxa[ls], cmxa[rs]);
return;
}
if(mxa[ls] > mxa[rs])
{
cntmx[now] = cntmx[ls];
cmxa[now] = max(cmxa[ls], mxa[rs]);
}
else
{
cntmx[now] = cntmx[rs];
cmxa[now] = max(mxa[ls], cmxa[rs]);
}
return;
}
然后是 U p d a t e Update Update,它的函数框架应该是这样的:
inline void Update(int now, int l, int r, LL pamx, LL panmx, LL pbmx, LL pbnmx)
( p p p 前缀代指 p l u s plus plus,即需要加的数)
首先维护 s u m a suma suma,用 p a m x pamx pamx 与 c n t m x cntmx cntmx 配合维护即可。
其次,我们要维护 m x b , t a g b m x , t a g b n m x mxb,tagbmx,tagbnmx mxb,tagbmx,tagbnmx 三个和 B i B_i Bi 有关的变量。
为什么呢?因为它们的维护与 A i A_i Ai 关系密切,而后面的操作会改变和 A i A_i Ai 有关的变量。
对于 m x b mxb mxb,我们把它和 m x a + p b m x mxa + pbmx mxa+pbmx 取最大值,后者即为上文所提到的“历史最大值的可能值”。
对于 t a g b m x tagbmx tagbmx,我们把它和 t a g a m x + p b m x tagamx + pbmx tagamx+pbmx 取最大值,原理同上。
同理,对于 t a g b n m x tagbnmx tagbnmx,我们把它和 t a g a n m x + p b n m x taganmx + pbnmx taganmx+pbnmx 取最大值即可。
然后和 A i A_i Ai 有关的变量维护起来就没那么麻烦了,直接加即可,注意判断 c m x a cmxa cmxa 的有无。
代码:
inline void Update(int now, int l, int r, LL pamx, LL panmx, LL pbmx, LL pbnmx)
{
suma[now] += pamx * cntmx[now] + panmx * (r - l + 1 - cntmx[now]);
mxb[now] = max(mxb[now], mxa[now] + pbmx);
tagbmx[now] = max(tagbmx[now], tagamx[now] + pbmx);
tagbnmx[now] = max(tagbnmx[now], taganmx[now] + pbnmx);
mxa[now] += pamx, tagamx[now] += pamx, taganmx[now] += panmx;
if(cmxa[now] != -INF) cmxa[now] += panmx;
return;
}
写出了 U p d a t e Update Update,其实 P u s h d o w n Pushdown Pushdown 也很好写了。
只要你真正理解了上述变量的含义,相信你看懂甚至独立写出下述代码都会易如反掌。
(注意左右儿子的 m x a mxa mxa 最大值要保存起来)
inline void Pushdown(int now, int l, int r)
{
int mxn = max(mxa[ls], mxa[rs]);
if(mxa[ls] == mxn) Update(ls, l, mid, tagamx[now], taganmx[now], tagbmx[now], tagbnmx[now]);
else Update(ls, l, mid, taganmx[now], taganmx[now], tagbnmx[now], tagbnmx[now]);
if(mxa[rs] == mxn) Update(rs, mid + 1, r, tagamx[now], taganmx[now], tagbmx[now], tagbnmx[now]);
else Update(rs, mid + 1, r, taganmx[now], taganmx[now], tagbnmx[now], tagbnmx[now]);
return tagamx[now] = taganmx[now] = tagbmx[now] = tagbnmx[now] = 0LL, void();
}
接下来我们分析一下每个操作。
首先是操作 1 1 1。
这是线段树的基本操作,可以直接做。
在 U p d a t e Update Update 函数中,我们对所有量一视同仁,加法标记(指函数参数)全部赋为 k k k。
代码:
inline void Modify_Plus(int now, int l, int r, int L, int R, LL k)
{
if(L <= l && r <= R) return Update(now, l, r, k, k, k, k);
Pushdown(now, l, r);
if(L <= mid) Modify_Plus(ls, l, mid, L, R, k);
if(R > mid) Modify_Plus(rs, mid + 1, r, L, R, k);
return Pushup(now);
}
接着分析一下操作 2 2 2。
我们对 v v v 分类讨论:(以下区间指线段树递归时遍历到的区间)
- 如果 v ≥ m x a v \ge mxa v≥mxa,那么该操作不会有任何影响,直接跳过该区间即可;
- 如果 c m x a < v < m x a cmxa < v < mxa cmxa<v<mxa,那么该操作只会改变最大值,不会改变 c n t m x cntmx cntmx;
- 如果 v ≤ c m x a v \le cmxa v≤cmxa,那么我们继续递归下去(线段树的叶子节点不会出现这种情况)。
对于最大值,加法标记即为 k − m x a k - mxa k−mxa(减去 m x a mxa mxa 加上 k k k,也就是把 m x a mxa mxa 赋为 k k k);对于非最大值,这样的修改不会有贡献,即加法标记为 0 0 0。
代码:
inline void Modify_Minimize(int now, int l, int r, int L, int R, LL k)
{
if(k >= mxa[now]) return;
if(L <= l && r <= R && k > cmxa[now]) return Update(now, l, r, k - mxa[now], 0, k - mxa[now], 0);
Pushdown(now, l, r);
if(L <= mid) Modify_Minimize(ls, l, mid, L, R, k);
if(R > mid) Modify_Minimize(rs, mid + 1, r, L, R, k);
return Pushup(now);
}
操作 3 , 4 , 5 3,4,5 3,4,5 实际上是同普通的线段树完全一样的套路。
代码:
inline LL Query_Sum(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R) return suma[now];
Pushdown(now, l, r);
LL res = 0LL;
if(L <= mid) res += Query_Sum(ls, l, mid, L, R);
if(R > mid) res += Query_Sum(rs, mid + 1, r, L, R);
return res;
}
inline LL Query_Maximum_A(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R) return mxa[now];
Pushdown(now, l, r);
LL res = -INF;
if(L <= mid) res = max(res, Query_Maximum_A(ls, l, mid, L, R));
if(R > mid) res = max(res, Query_Maximum_A(rs, mid + 1, r, L, R));
return res;
}
inline LL Query_Maximum_B(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R) return mxb[now];
Pushdown(now, l, r);
LL res = -INF;
if(L <= mid) res = max(res, Query_Maximum_B(ls, l, mid, L, R));
if(R > mid) res = max(res, Query_Maximum_B(rs, mid + 1, r, L, R));
return res;
}
当然还有 B u i l d Build Build 函数,放总代码里啦。
总代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#include<algorithm>
#define MAXN 500005
using LL = long long;
using namespace std;
int n, m;
const LL INF = 0x3F3F3F3F3F3F3F3F;
namespace Segment_Tree_Beats
{
#define ls (now << 1)
#define rs (now << 1 | 1)
#define mid ((l + r) >> 1)
int cntmx[MAXN << 2];
LL suma[MAXN << 2], mxa[MAXN << 2], cmxa[MAXN << 2], mxb[MAXN << 2], tagamx[MAXN << 2], taganmx[MAXN << 2], tagbmx[MAXN << 2], tagbnmx[MAXN << 2];
inline void Pushup(int now)
{
suma[now] = suma[ls] + suma[rs];
mxa[now] = max(mxa[ls], mxa[rs]);
mxb[now] = max(mxb[ls], mxb[rs]);
if(mxa[ls] == mxa[rs])
{
cntmx[now] = cntmx[ls] + cntmx[rs];
cmxa[now] = max(cmxa[ls], cmxa[rs]);
return;
}
if(mxa[ls] > mxa[rs])
{
cntmx[now] = cntmx[ls];
cmxa[now] = max(cmxa[ls], mxa[rs]);
}
else
{
cntmx[now] = cntmx[rs];
cmxa[now] = max(mxa[ls], cmxa[rs]);
}
return;
}
inline void Update(int now, int l, int r, LL pamx, LL panmx, LL pbmx, LL pbnmx)
{
suma[now] += pamx * cntmx[now] + panmx * (r - l + 1 - cntmx[now]);
mxb[now] = max(mxb[now], mxa[now] + pbmx);
tagbmx[now] = max(tagbmx[now], tagamx[now] + pbmx);
tagbnmx[now] = max(tagbnmx[now], taganmx[now] + pbnmx);
mxa[now] += pamx, tagamx[now] += pamx, taganmx[now] += panmx;
if(cmxa[now] != -INF) cmxa[now] += panmx;
return;
}
inline void Pushdown(int now, int l, int r)
{
int mxn = max(mxa[ls], mxa[rs]);
if(mxa[ls] == mxn) Update(ls, l, mid, tagamx[now], taganmx[now], tagbmx[now], tagbnmx[now]);
else Update(ls, l, mid, taganmx[now], taganmx[now], tagbnmx[now], tagbnmx[now]);
if(mxa[rs] == mxn) Update(rs, mid + 1, r, tagamx[now], taganmx[now], tagbmx[now], tagbnmx[now]);
else Update(rs, mid + 1, r, taganmx[now], taganmx[now], tagbnmx[now], tagbnmx[now]);
return tagamx[now] = taganmx[now] = tagbmx[now] = tagbnmx[now] = 0LL, void();
}
inline void Build(int now, int l, int r)
{
if(l == r)
{
LL k; scanf("%lld", &k);
return suma[now] = mxa[now] = mxb[now] = k, cntmx[now] = 1, cmxa[now] = -INF, tagamx[now] = taganmx[now] = tagbmx[now] = tagbnmx[now] = 0LL, void();
}
return Build(ls, l, mid), Build(rs, mid + 1, r), Pushup(now);
}
inline void Modify_Plus(int now, int l, int r, int L, int R, LL k)
{
if(L <= l && r <= R) return Update(now, l, r, k, k, k, k);
Pushdown(now, l, r);
if(L <= mid) Modify_Plus(ls, l, mid, L, R, k);
if(R > mid) Modify_Plus(rs, mid + 1, r, L, R, k);
return Pushup(now);
}
inline void Modify_Minimize(int now, int l, int r, int L, int R, LL k)
{
if(k >= mxa[now]) return;
if(L <= l && r <= R && k > cmxa[now]) return Update(now, l, r, k - mxa[now], 0, k - mxa[now], 0);
Pushdown(now, l, r);
if(L <= mid) Modify_Minimize(ls, l, mid, L, R, k);
if(R > mid) Modify_Minimize(rs, mid + 1, r, L, R, k);
return Pushup(now);
}
inline LL Query_Sum(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R) return suma[now];
Pushdown(now, l, r);
LL res = 0LL;
if(L <= mid) res += Query_Sum(ls, l, mid, L, R);
if(R > mid) res += Query_Sum(rs, mid + 1, r, L, R);
return res;
}
inline LL Query_Maximum_A(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R) return mxa[now];
Pushdown(now, l, r);
LL res = -INF;
if(L <= mid) res = max(res, Query_Maximum_A(ls, l, mid, L, R));
if(R > mid) res = max(res, Query_Maximum_A(rs, mid + 1, r, L, R));
return res;
}
inline LL Query_Maximum_B(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R) return mxb[now];
Pushdown(now, l, r);
LL res = -INF;
if(L <= mid) res = max(res, Query_Maximum_B(ls, l, mid, L, R));
if(R > mid) res = max(res, Query_Maximum_B(rs, mid + 1, r, L, R));
return res;
}
#undef ls
#undef rs
#undef mid
}
namespace stb = Segment_Tree_Beats;
int main()
{
#ifdef FILE
freopen("Input.in", "r", stdin);
#endif
scanf("%d%d", &n, &m);
stb :: Build(1, 1, n);
while(m--)
{
int opt, l, r; scanf("%d%d%d", &opt, &l, &r);
if(opt == 1)
{
LL k; scanf("%lld", &k);
stb :: Modify_Plus(1, 1, n, l, r, k);
continue;
}
if(opt == 2)
{
LL k; scanf("%lld", &k);
stb :: Modify_Minimize(1, 1, n, l, r, k);
continue;
}
if(opt == 3) printf("%lld\n", stb :: Query_Sum(1, 1, n, l, r));
if(opt == 4) printf("%lld\n", stb :: Query_Maximum_A(1, 1, n, l, r));
if(opt == 5) printf("%lld\n", stb :: Query_Maximum_B(1, 1, n, l, r));
}
return 0;
}
3455



被折叠的 条评论
为什么被折叠?



