Description

​P2042 [NOI2005] 维护数列​

Solution

一道细节巨多的毒瘤题。

我用的 \(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
#include <bits/stdc++.h>
#define ls(x) t[x].ch[0]
#define rs(x) t[x].ch[1]

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;
}


End