一道细节巨多的毒瘤题。
我用的 \(fhq-treap\) 通过的此题。
下面我们以此分析一下。
先说一下 \(fhq-treap\) 中需要存什么东西。
struct Treap{
int ch[2], siz, val, wei, maxqz, maxhz, maxzd, sum, cov;
bool lazy, tag;//lazy: 翻转标记 tag: 区间覆盖标记
}t[N];
- ch[2]: 左右子树。
- siz: 子树大小。
- val: 当前节点权值。
- wei: 随机值。(合并用的,非常玄学)
- maxqz,maxhz,sum: 最大前缀和,最大后缀和,区间和。(为计算最大子段和做铺垫)
- maxzd: 最大子段和。
- cov: 覆盖操作的值。
\(bool\) 类型两个变量上面有注释。
操作就不分析了,直接来看函数吧。
pushup
先放代码。
inline void pushup(int x){
if(!x) return;
t[x].siz = t[ls(x)].siz + t[rs(x)].siz + 1;
t[x].sum = t[ls(x)].sum + t[x].val + t[rs(x)].sum;
t[x].maxqz = max(max(t[ls(x)].maxqz, t[ls(x)].sum + t[x].val + t[rs(x)].maxqz), 0);
t[x].maxhz = max(max(t[rs(x)].maxhz, t[rs(x)].sum + t[x].val + t[ls(x)].maxhz), 0);
t[x].maxzd = max(t[x].val, t[ls(x)].maxhz + t[x].val + t[rs(x)].maxqz);
if(ls(x)) t[x].maxzd = max(t[x].maxzd, t[ls(x)].maxzd);
if(rs(x)) t[x].maxzd = max(t[x].maxzd, t[rs(x)].maxzd);
}
看着就心烦。
\(siz,sum\) 维护就不多说了。
\(父节点_{maxqz} = max(左子树_{maxqz}, 左子树_{sum} + 父节点_{val} + 右子树_{maxqz})\),再和 0 取较大值(不理解的话可以自己手模一下)。
\(maxhz\) 同理。
\(父节点_{maxzd} = max(父节点_{val},左子树_{maxhz} + 父节点_{val} + 右子树_{maxqz})\)
如果有左右子树的话,就再和左右子树中的最大子段和取最大值。
这样就不难理解了吧,看起来是不是还挺顺眼的。
翻转操作
inline void Reverse(int x){
if(!x) return;
swap(ls(x), rs(x));//交换左右孩子
swap(t[x].maxqz, t[x].maxhz);//交换前缀和后缀
t[x].lazy ^= 1;//翻转两次等于没翻转,所以异或 1
}
区间覆盖操作
inline void Cover(int x, int k){
t[x].val = t[x].cov = k, t[x].sum = t[x].siz * k;//当前节点值和覆盖值都赋值为 k,区间和 = 区间大小 * k
t[x].maxqz = t[x].maxhz = max(0, t[x].sum);//显然如果 k > 0,最大前后缀和就是区间和,反之就是 0
t[x].maxzd = max(t[x].val, t[x].sum);//跟最大前后缀和差不多,但是由于必须选一个,所以跟 val 取较大值,而不是 0
t[x].tag = 1;//覆盖标记打为 1
}
删除操作
inline void Delete(int x){
if(!x) return;
stk[++top] = x;//这里一会再说,这句话相当于删除了这个点
if(ls(x)) Delete(ls(x));//如果有左右子树,就删除
if(rs(x)) Delete(rs(x));
}
pushdown
inline void pushdown(int x){
if(!x) return;
if(t[x].lazy){//如果有翻转标记
if(ls(x)) Reverse(ls(x));
if(rs(x)) Reverse(rs(x));
t[x].lazy = 0;
}
if(t[x].tag){//如果有覆盖标记
if(ls(x)) Cover(ls(x), t[x].cov);
if(rs(x)) Cover(rs(x), t[x].cov);
t[x].tag = t[x].cov = 0;
}
}
比较好理解吧。
分裂 & 合并操作
这两个由于没什么变化就一起说了,\(pushdown\) 随时写着点。
inline void split(int x, int k, int &a, int &b){
if(!x){
a = b = 0;
return;
}
pushdown(x);
if(k >= t[ls(x)].siz + 1){
a = x;
split(rs(x), k - t[ls(x)].siz - 1, rs(x), b);
}else{
b = x;
split(ls(x), k, a, ls(x));
}
pushup(x);
}
inline int merge(int x, int y){
if(!x || !y) return x | y;
if(t[x].wei <= t[y].wei){
pushdown(x);
rs(x) = merge(rs(x), y);
pushup(x);
return x;
}else{
pushdown(y);
ls(y) = merge(x, ls(y));
pushup(y);
return y;
}
}
建新节点(newnode)
这个就有点意思了。
赋值为 0 的语句一个都不能少(原因一会和上面删除那里一起说)
inline int newnode(int k){
tot = stk[top--];//这个也一会再说
t[tot].wei = rand();
ls(tot) = rs(tot) = t[tot].lazy = t[tot].tag = 0;//这句话千万不能少
t[tot].val = t[tot].sum = k, t[tot].siz = 1;
t[tot].maxqz = t[tot].maxhz = max(0, t[tot].sum);
t[tot].maxzd = max(t[tot].val, t[tot].sum);
return tot;
}
其它语句跟区间覆盖都差不多,可以看那里理解一下这个。
建树操作(build)
inline int build(int l, int r){
if(l == r)
return newnode(p[l]);//p 是输入的数组
int mid = (l + r) >> 1;
return merge(build(l, mid), build(mid + 1, r));//返回根
}
各个操作都已经分析完了,我们来解释一下上面的问题。
一句话说:这道题\(\huge{卡空间,卡空间,卡空间!!!}\)
那么我们怎么办呢?
很简单就像上面那样不就完了?
我们开个栈,把不用的点都压进去,使用的时候再弹出来。
这样就有了上面的写法。
删除时:直接压到栈里面。
新建节点时:直接把节点编号赋值成栈顶元素。
然后……这道题不仅卡空间,还卡\(\huge{时间!!}\)
体现在我们区间覆盖时,如果先全部删除,再插入就会 \(TLE\)。
所以我们要用打标记,\(pushdown\) 不停向下处理。
主函数就不用多说了吧,自己看着理解吧。
刚开始的时候把所有的数都加到栈中。
Code
using namespace std;
const int N = 5e5 + 10;
int stk[N], top;
int n, m, root, tot;
int p[N];
struct Treap{
int ch[2], siz, val, wei, maxqz, maxhz, maxzd, sum, cov;
bool lazy, tag;//lazy: 翻转标记 tag: 区间覆盖标记
}t[N];
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
inline void pushup(int x){
if(!x) return;
t[x].siz = t[ls(x)].siz + t[rs(x)].siz + 1;
t[x].sum = t[ls(x)].sum + t[x].val + t[rs(x)].sum;
t[x].maxqz = max(max(t[ls(x)].maxqz, t[ls(x)].sum + t[x].val + t[rs(x)].maxqz), 0);
t[x].maxhz = max(max(t[rs(x)].maxhz, t[rs(x)].sum + t[x].val + t[ls(x)].maxhz), 0);
t[x].maxzd = max(t[x].val, t[ls(x)].maxhz + t[x].val + t[rs(x)].maxqz);
if(ls(x)) t[x].maxzd = max(t[x].maxzd, t[ls(x)].maxzd);
if(rs(x)) t[x].maxzd = max(t[x].maxzd, t[rs(x)].maxzd);
}
inline void Reverse(int x){
if(!x) return;
swap(ls(x), rs(x));
swap(t[x].maxqz, t[x].maxhz);
t[x].lazy ^= 1;
}
inline void Cover(int x, int k){
t[x].val = t[x].cov = k, t[x].sum = t[x].siz * k;
t[x].maxqz = t[x].maxhz = max(0, t[x].sum);
t[x].maxzd = max(t[x].val, t[x].sum);
t[x].tag = 1;
}
inline void Delete(int x){
// cout<<"xx "<<x<<endl;
if(!x) return;
stk[++top] = x;
if(ls(x)) Delete(ls(x));
if(rs(x)) Delete(rs(x));
}
inline void pushdown(int x){
if(!x) return;
if(t[x].lazy){
if(ls(x)) Reverse(ls(x));
if(rs(x)) Reverse(rs(x));
t[x].lazy = 0;
}
if(t[x].tag){
if(ls(x)) Cover(ls(x), t[x].cov);
if(rs(x)) Cover(rs(x), t[x].cov);
t[x].tag = t[x].cov = 0;
}
}
inline void split(int x, int k, int &a, int &b){
if(!x){
a = b = 0;
return;
}
pushdown(x);
if(k >= t[ls(x)].siz + 1){
a = x;
split(rs(x), k - t[ls(x)].siz - 1, rs(x), b);
}else{
b = x;
split(ls(x), k, a, ls(x));
}
pushup(x);
}
inline int merge(int x, int y){
if(!x || !y) return x | y;
if(t[x].wei <= t[y].wei){
pushdown(x);
rs(x) = merge(rs(x), y);
pushup(x);
return x;
}else{
pushdown(y);
ls(y) = merge(x, ls(y));
pushup(y);
return y;
}
}
inline int newnode(int k){
tot = stk[top--];
t[tot].wei = rand();
ls(tot) = rs(tot) = t[tot].lazy = t[tot].tag = 0;
t[tot].val = t[tot].sum = k, t[tot].siz = 1;
t[tot].maxqz = t[tot].maxhz = max(0, t[tot].sum);
t[tot].maxzd = max(t[tot].val, t[tot].sum);
return tot;
}
inline int build(int l, int r){
if(l == r)
return newnode(p[l]);
int mid = (l + r) >> 1;
return merge(build(l, mid), build(mid + 1, r));
}
int a, b, c;
int main(){
n = read(), m = read();
for(int i = 1; i <= 5e5; i++)
stk[++top] = i;
for(int i = 1; i <= n; i++)
p[i] = read();
root = merge(build(1, n), root);
char op[10];
while(m--){
scanf("%s", op);
if(op[0] == 'I'){
int pos = read(), cnt = read();
split(root, pos, a, b);
for(int i = 1; i <= cnt; i++)
p[i] = read();
root = merge(merge(a, build(1, cnt)), b);
}else if(op[0] == 'D'){
int pos = read(), cnt = read();
split(root, pos - 1, a, b);
split(b, cnt, b, c);
Delete(b);
root = merge(a, c);
}else if(op[0] == 'M' && op[2] == 'K'){
int pos = read(), cnt = read(), k = read();
split(root, pos - 1, a, b);
split(b, cnt, b, c);
Cover(b, k);
root = merge(merge(a, b), c);
}else if(op[0] == 'R'){
int pos = read(), cnt = read();
split(root, pos - 1, a, b);
split(b, cnt, b, c);
Reverse(b);
root = merge(merge(a, b), c);
}else if(op[0] == 'G'){
int pos = read(), cnt = read();
split(root, pos - 1, a, b);
split(b, cnt, b, c);
printf("%d\n", t[b].sum);
root = merge(merge(a, b), c);
}else printf("%d\n", t[root].maxzd);
}
return 0;
}