Splay P2042 [NOI2005] 维护数列
开坑!!!
\(\mathscr{NOI2005}\) 维护数列
\(1.\) 题意
维护一个数列,要求支持
- \(INSERT\) 在 \(posi\) 后插入 \(tot\) 个数字 \(c_1, c_2 \cdots c_{tot}\)。
- \(DELETE\) 删除 \(posi\) 后连续 \(tot\) 个数字。
- \(MAKE-SAME\) 从 \(posi\) 后连续 \(tot\) 个数字改为 \(c\)。
- \(REVERSE\) 从 \(posi\) 后 \(tot\) 个数字翻转。
- \(GET-SUM\) 计算从 \(posi\) 后 \(tot\) 个数字的和。
- \(MAX-SUM\) 求出最大子序列和。
\(2.\) 思路
考虑 \(splay\) 每个节点需要维护的信息。
- \(ms\) 最大子段和
- \(ls\) 最大前缀和
- \(rs\) 最大后缀和
- \(sum\) 区间和
- \(size\) 区间节点个数
- \(rev\) 翻转的懒标记
- \(same\) 整个区间是否变成根相同的数
- \(s[2], p, v\) 常规的,左右儿子,祖先,权值
之后考虑,如果一个点有懒标记,节点存储的是懒标记操作之前的值,还是懒标记操作之后的值。本题的操作会影响存储的值,比如 \(rev\) 会影响 \(ls, rs\),本题记录懒标记操作之后的值。这样写比较方便。
每次懒标记标记后需要把节点维护的每一个值都算一遍。以保证标记后对应操作后的值仍然正确。对于每次 \(pushdown\) 不仅要标记上子节点,还要更新子节点的 \(1-4\) 信息。需要保证子节点的信息正确性,保证子节点推父节点的信息正确。
- 对于给定区间建立二叉平衡树,如线段树一样递归建立即可。
- \(splay\) 的内存回收机制:开一个数组存可用的下标,初始化为 \(1-n\),每次删除将子树的所有节点编号存入该数组,每次插入寻找可用下标即可。
操作 \(1\) 实现:
在 \(posi\) 后插入 \(tot\) 个数字 \(c_1, c_2 \cdots c_{tot}\)。
设 \(L = posi, R = posi+1\),将 \(L\) 转到根,将 \(R\) 转到 \(L\) 的下方,这是 \(R\) 的左子树是空的,将需要插入的序列建立平衡二叉树插入 \(R\) 的左子树,之后 \(pushup\) \(L,R\) 的信息即可。
操作 \(2\) 实现:
删除 \(posi\) 后连续 \(tot\) 个数字。
设 \(L = posi - 1, R = posi + tot + 1\),将 \(L\) 转到根,将 \(R\) 转到 \(L\) 的下方,删除 \(R\) 的左子树即可。记得 \(pushup\)。
操作 \(3\) 实现:
从 \(posi\) 后连续 \(tot\) 个数字改为 \(c\)。
设 \(L = posi - 1, R = posi + tot + 1\),将 \(L\) 转到根,将 \(R\) 转到 \(L\) 的下方,将 \(R\) 的左子树根节点打上 \(same\) 标记即可,记得改变当前节点的信息。
理解以上过程,代码很好写,但是好调就不一定了,在晚自习下课前刚出来了,嗨害嗨
有轻微注释
#include<map>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
template<class T> inline void read(T &x){
x = 0; register char c = getchar(); register bool f = 0;
while(!isdigit(c)) f ^= c == '-', c = getchar();
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
if(f) x = -x;
}
template<class T> inline void print(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) print(x / 10);
putchar('0' + x % 10);
}
const int N = 5e5 + 10;
const int inf = 1e9;
int n, m;
struct Node{
int s[2], p, v;
int rev, same;
int size, sum, ms, ls, rs;
inline void init(int _v, int _p){ //初始化
s[0] = s[1] = 0, p = _p, v = _v;
rev = same = 0;
size = 1, sum = ms = v;
ls = rs = max(v, 0);
}
}tr[N];
int root, nodes[N], tt; //回收站
int w[N];
inline void pushup(int x){ // 注意信息维护全,并且注意顺序
auto &u = tr[x], &l = tr[u.s[0]], &r = tr[u.s[1]];
u.size = l.size + r.size + 1;
u.sum = l.sum + r.sum + u.v;
u.ls = max(l.ls, l.sum + u.v + r.ls);
u.rs = max(r.rs, r.sum + u.v + l.rs);
u.ms = max(max(l.ms, r.ms), l.rs + u.v + r.ls);
}
inline void pushdown(int x){ //注意细节
auto &u = tr[x], &l = tr[u.s[0]], &r = tr[u.s[1]];
if (u.same){
u.same = u.rev = 0;
if (u.s[0]) l.same = 1, l.v = u.v, l.sum = l.v * l.size;
if (u.s[1]) r.same = 1, r.v = u.v, r.sum = r.v * r.size;
if (u.v > 0){
if (u.s[0]) l.ms = l.ls = l.rs = l.sum;
if (u.s[1]) r.ms = r.ls = r.rs = r.sum;
}else{
if (u.s[0]) l.ms = l.v, l.ls = l.rs = 0;
if (u.s[1]) r.ms = r.v, r.ls = r.rs = 0;
}
}else if (u.rev){
u.rev = 0, l.rev ^= 1, r.rev ^= 1;
swap(l.ls, l.rs), swap(r.ls, r.rs);
swap(l.s[0], l.s[1]), swap(r.s[0], r.s[1]);
}
}
inline void rotate(int x){
int y = tr[x].p, z = tr[y].p;
int k = tr[y].s[1] == x;
tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
tr[x].s[k ^ 1] = y, tr[y].p = x;
pushup(y), pushup(x);
}
inline void splay(int x, int k){
while (tr[x].p != k){
int y = tr[x].p, z = tr[y].p;
if (z != k)
if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
else rotate(y);
rotate(x);
}
if (!k) root = x;
}
inline int get_k(int k){
int u = root;
while (u){
pushdown(u);
if (tr[tr[u].s[0]].size >= k) u = tr[u].s[0];
else if (tr[tr[u].s[0]].size + 1 == k) return u;
else k -= tr[tr[u].s[0]].size + 1, u = tr[u].s[1];
}
}
inline int build(int l, int r, int p){
int mid = l + r >> 1;
int u = nodes[tt -- ]; //根节点从回收站拿一个点
tr[u].init(w[mid], p); //初始化值,父节点
if (l < mid) tr[u].s[0] = build(l, mid - 1, u);
if (mid < r) tr[u].s[1] = build(mid + 1, r, u);
pushup(u);
return u;
}
inline void dfs(int u){ //内存回收
if (tr[u].s[0]) dfs(tr[u].s[0]); //有左回收左
if (tr[u].s[1]) dfs(tr[u].s[1]); //有右回收右
nodes[ ++ tt] = u; //回收当前
}
int main(){
for (int i = 1; i < N; i ++ ) nodes[ ++ tt] = i; //存进回收站
read(n), read(m);
tr[0].ms = w[0] = w[n + 1] = -inf;
for (int i = 1; i <= n; i ++ ) read(w[i]);
root = build(0, n + 1, 0); //建立二叉平衡树
char op[20];
while (m -- ){
scanf("%s", op);
if (!strcmp(op, "INSERT")){
int posi, tot; read(posi), read(tot);
for (int i = 0; i < tot; i ++ ) read(w[i]);
int l = get_k(posi + 1), r = get_k(posi + 2); //找到 L,R 因为有哨兵需要+1
splay(l, 0), splay(r, l); //将L转到根,R到L下方
int u = build(0, tot - 1, r); //以R为根建树
tr[r].s[0] = u;
pushup(r), pushup(l);
}else if (!strcmp(op, "DELETE")){
int posi, tot; read(posi), read(tot);
int l = get_k(posi), r = get_k(posi + tot + 1); //找到L,R
splay(l, 0), splay(r, l); //将L转到根,R到L下方
dfs(tr[r].s[0]);
tr[r].s[0] = 0;
pushup(r), pushup(l);
}else if (!strcmp(op, "MAKE-SAME")){
int posi, tot, c; read(posi), read(tot), read(c);
int l = get_k(posi), r = get_k(posi + tot + 1);
splay(l, 0), splay(r, l);
auto& son = tr[tr[r].s[0]]; //R的左儿子
son.same = 1, son.v = c, son.sum = c * son.size; //更新当前节点
if (c > 0) son.ms = son.ls = son.rs = son.sum;
else son.ms = c, son.ls = son.rs = 0;
pushup(r), pushup(l);
}else if (!strcmp(op, "REVERSE")){
int posi, tot; read(posi), read(tot);
int l = get_k(posi), r = get_k(posi + tot + 1);
splay(l, 0), splay(r, l);
auto& son = tr[tr[r].s[0]];
son.rev ^= 1; //打标记
swap(son.ls, son.rs); //交换前缀最大后缀最大
swap(son.s[0], son.s[1]); //交换左右子树
pushup(r), pushup(l);
}else if (!strcmp(op, "GET-SUM")){
int posi, tot; read(posi), read(tot);
int l = get_k(posi), r = get_k(posi + tot + 1);
splay(l, 0), splay(r, l);
print(tr[tr[r].s[0]].sum), puts("");
} else print(tr[root].ms), puts("");
}
return 0;
}