Acwing955 维护数列 题解

题面

传送门
要求维护一个数列,支持以下6种操作:

  1. 插入:在当前数列的第\(posi\)个数字后插入\(tot\)个给定数字\(c_1,c_2,...,c_{tot}\)
  2. 删除:从当前数列的第\(posi\)个数字开始,连续删除\(tot\)个数字
  3. 修改:将当前数列的第\(posi\)个数字开始的\(tot\)个数字全部修改为\(c\)
  4. 翻转:将当前数列的第\(posi\)个数字开始的\(tot\)个数字翻转
  5. 求和:输出当前数列的第\(posi\)个数字开始的\(tot\)个数字的和
  6. 求最大子序和:输出当前数列的最大子序和,长度不能为0

在任何时候,数列的长度在[1,500000]内。所有数字均在[-1000,1000]内。保证操作次数M≤20000,插入的数字总数≤4000000。保证有解。

题解

看到“插入”“删除”“翻转”,自然想到用Splay。Splay中维护元素的下标有序,就可以实现区间的插入和删除了。
借鉴线段树的思想,还可以实现3456操作。用\(a[x].sum\)表示以x为根的子树的数值总和,\(a[x].ms\)表示最大子序和,\(a[x].ls\)表示最大前缀和,\(a[x].rs\)表示最大后缀和。再使用懒惰标记\(a[x].rev\)表示是否需要翻转,\(a[x].same\)表示是否要将其子树改为和x数值相同。那么所有操作可以\(O(\log{N})\)实现。
还有一点需要注意:更新的顺序需要与懒惰标记表示的实际意义相对应。此解法中,懒惰标记表示“当前节点x已更新,但其子节点未更新”,那么在34操作之初,就需要更新节点的信息。pushdown(int x)时,更新的是x的子节点的信息。切记不能混淆。

void pushup(int x) {
    node &p = a[x], l = a[p.s[0]], r = a[p.s[1]];
    p.size = l.size + r.size + 1;
    p.sum = l.sum + r.sum + p.v;
    p.ls = max(l.ls, l.sum + p.v + r.ls);
    p.rs = max(r.rs, r.sum + p.v + l.rs);
    p.ms = max(max(l.ms, r.ms), l.rs + p.v + r.ls);
}
void pushdown(int x) {
    node &p = a[x], &l = a[p.s[0]], &r = a[p.s[1]];
    if (p.rev) {
        l.rev ^= 1; r.rev ^= 1;
        swap(l.s[0], l.s[1]); swap(l.ls, l.rs);
        swap(r.s[0], r.s[1]); swap(r.ls, r.rs);
        p.rev = 0;
    }
    if (p.same) {
        l.same = r.same = 1;
        l.v = r.v = p.v;
        l.sum = p.v * l.size; r.sum = p.v * r.size;
        l.ms = p.v > 0 ? l.sum : p.v; r.ms = p.v > 0 ? r.sum : p.v;
        l.ls = l.rs = (p.v > 0) * l.sum; r.ls = r.rs = (p.v > 0) * r.sum;
        if (!p.s[0] || !p.s[1]) {
            a[0].ms = -1e9; a[0].sum = a[0].ls = a[0].rs = 0;
        } //题目要求子序列长度不能为0。在pushup中,若l/r=0,则表示子节点为空,不能从其更新答案。对节点0的恰当赋值可以使代码更简短
        p.same = 0;
    }
}

另外,此题插入的数字总数较大,但实时的数列长度保持在较小范围内。在删除之后,很多空间被浪费掉了。于是我们可以执行内存回收,即将删除后的节点保留,在需要新建节点时,优先使用保留下来的节点。

void Recycle(int x) { //在执行“删除”操作之后
    if (!x) return ;
    rec[++top] = x; //rec,回收容器
    Recycle(a[x].s[0]); Recycle(a[x].s[1]); //递归实现
}

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 5e5 + 5;
struct node {
    int s[2], p, v, size;
    int rev, same, sum, ms, ls, rs;
    void init(int _v, int _p) {
        s[0] = s[1] = rev = same = 0;
        v = sum = ms = _v; p = _p;
        ls = rs = max(v, 0);
        size = 1;
    }
} a[N];
int m, root, rec[N], top;
int n, w[N];
void pushup(int x) {
    node &p = a[x], l = a[p.s[0]], r = a[p.s[1]];
    p.size = l.size + r.size + 1;
    p.sum = l.sum + r.sum + p.v;
    p.ls = max(l.ls, l.sum + p.v + r.ls);
    p.rs = max(r.rs, r.sum + p.v + l.rs);
    p.ms = max(max(l.ms, r.ms), l.rs + p.v + r.ls);
}
void pushdown(int x) {
    node &p = a[x], &l = a[p.s[0]], &r = a[p.s[1]];
    if (p.rev) {
        l.rev ^= 1; r.rev ^= 1;
        swap(l.s[0], l.s[1]); swap(l.ls, l.rs);
        swap(r.s[0], r.s[1]); swap(r.ls, r.rs);
        p.rev = 0;
    }
    if (p.same) {
        l.same = r.same = 1;
        l.v = r.v = p.v;
        l.sum = p.v * l.size; r.sum = p.v * r.size;
        l.ms = p.v > 0 ? l.sum : p.v; r.ms = p.v > 0 ? r.sum : p.v;
        l.ls = l.rs = (p.v > 0) * l.sum; r.ls = r.rs = (p.v > 0) * r.sum;
        if (!p.s[0] || !p.s[1]) {
            a[0].ms = -1e9; a[0].sum = a[0].ls = a[0].rs = 0;
        }
        p.same = 0;
    }
}
void rotate(int x) {
    int y = a[x].p, z = a[y].p, k = a[y].s[1] == x;
    a[z].s[a[z].s[1] == y] = x; a[x].p = z;
    a[y].s[k] = a[x].s[k ^ 1]; a[a[x].s[k ^ 1]].p = y;
    a[x].s[k ^ 1] = y; a[y].p = x;
    pushup(y); pushup(x);
}
void Splay(int x, int k) {
    while (a[x].p != k) {
        int y = a[x].p, z = a[y].p;
        if (z != k) {
            if (a[z].s[1] == y ^ a[y].s[1] == x) rotate(x);
            else rotate(y);
        }
        rotate(x);
    }
    if (!k) root = x;
}
int Build(int l, int r, int fa) {
    if (l > r) return 0;
    int mid = l + r >> 1;
    int x = rec[top--];
    a[x].init(w[mid], fa);
    a[x].s[0] = Build(l, mid - 1, x); a[x].s[1] = Build(mid + 1, r, x);
    pushup(x);
    return x;
}
int Get_k(int k) {
    int p = root;
    while (p) {
        pushdown(p);
        if (a[a[p].s[0]].size >= k) p = a[p].s[0];
        else if (a[a[p].s[0]].size + 1 == k) return p;
        else {
            k -= a[a[p].s[0]].size + 1; p = a[p].s[1];
        }
    }
}
void Recycle(int x) {
    if (!x) return ;
    rec[++top] = x;
    Recycle(a[x].s[0]); Recycle(a[x].s[1]);
}
int main() {
    char opt[15];
    int pos, c;
    for (int i = 5e5; i >= 1; i--) rec[++top] = i;
    scanf("%d%d", &n, &m);
    a[0].ms = w[0] = w[n + 1] = -1e9;
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
    root = Build(0, n + 1, 0);
    while (m--) {
        scanf("%s", opt);
        if (opt[0] == 'I') {
            scanf("%d%d", &pos, &n);
            for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
            int x = Get_k(pos + 1), y = Get_k(pos + 2);
            Splay(x, 0); Splay(y, x);
            a[y].s[0] = Build(1, n, y);
            pushup(y); pushup(x);
        }
        else if (opt[0] == 'D') {
            scanf("%d%d", &pos, &n);
            int x = Get_k(pos), y = Get_k(pos + n + 1);
            Splay(x, 0); Splay(y, x);
            Recycle(a[y].s[0]);
            a[y].s[0] = 0;
            pushup(y); pushup(x);
        }
        else if (opt[0] == 'R') {
            scanf("%d%d", &pos, &n);
            int x = Get_k(pos), y = Get_k(pos + n + 1);
            Splay(x, 0); Splay(y, x);
            node &cur = a[a[y].s[0]];
            cur.rev ^= 1;
            swap(cur.s[0], cur.s[1]);
            swap(cur.ls, cur.rs);
            pushup(y); pushup(x);
        }
        else if (opt[0] == 'G') {
            scanf("%d%d", &pos, &n);
            int x = Get_k(pos), y = Get_k(pos + n + 1);
            Splay(x, 0); Splay(y, x);
            printf("%d\n", a[a[y].s[0]].sum);
        }
        else if (opt[2] == 'K') {
            scanf("%d%d%d", &pos, &n, &c);
            int x = Get_k(pos), y = Get_k(pos + n + 1);
            Splay(x, 0); Splay(y, x);
            node &cur = a[a[y].s[0]];
            cur.same = 1; cur.v = c;
            cur.sum = cur.size * c;
            cur.ms = c > 0 ? cur.sum : c;
            cur.ls = cur.rs = (c > 0) * cur.sum;
            pushup(y); pushup(x);
        }
        else printf("%d\n", a[root].ms);
    }
    return 0;
}

可以发现,Splay可以实现线段树的操作,且还能实现很多线段树不能实现的操作,且只需一倍空间。就复杂度而言,Splay似乎碾压线段树。但Splay常数较大,效率大约只有线段树的三分之一,且代码量大,这是它的劣势。
这题的细节很多,调错非常难受……但这类区间操作可谓是Splay的精华,若能完全掌握并能触类旁通,合理地建模,那么Splay的难题也就至此为止了。

posted @ 2022-03-22 21:14  realFish  阅读(31)  评论(0)    收藏  举报