BZOJ 1500 Luogu P2042 [NOI2005] 维护数列 (Splay)

9 篇文章 0 订阅
1 篇文章 0 订阅

题目链接: (luogu) https://www.luogu.org/problem/show?pid=2042
(bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=1500

思路分析:
这个题嘛。。思路没啥好说的
用splay每个点维护四个量: sum[0..3] s u m [ 0..3 ] , sum[3] s u m [ 3 ] 表示splay整个子树代表的区间内元素之和; sum[1] s u m [ 1 ] sum[2] s u m [ 2 ] 分别表示这个区间内以左、右端点开始的元素最大和; sum[0] s u m [ 0 ] 表示这个区间内(不限端点)的最大子段和。
比如:序列是

a: 1 -4 -2 9 -5 -7 -999 666 -999 3 0
sum[3]=1+(-4)+(-2)+8+(-5)+(-7)+(-999)+666+(-999)+3+0=-1338
sum[2]=3+0=3
sum[1]=1+(-4)+(-2)+9=4
sum[0]=666=666

区间合并的话,我们可以先想想线段树怎么合并两段区间,分类讨论即可。平衡树由于根节点上还有值,因此合并两段区间+一个值,稍微麻烦点。(这部分略去,不会的可以去做bzoj 1756)
然后就可以开心地码啦!

部分易错点
  1. 由于所有插入的元素可能达到 4×106 4 × 10 6 个, 如果建这么多个splay节点,每一个开 int i n t 数组记录,则每个节点维护每个值就会花 16MB 16 M B 空间,然而空间限制 128MB 128 M B , 也就是我们至多维护 7 7 个量。(什么你说8个??你128MB空间全开了这一个数组,多开一个字节就MLE了啊)而至少我没有想出用每个节点 7 7 个量维护的方法。貌似开fa,son[2],sum[4],tag就已经 8 8 个了啊..
    解决办法: 手写内存回收池, 对于已经删除的节点,把它clear掉并把编号放到一个内存回收池中,insert时先从内存回收池中取出一个编号来用,如果内存回收池为空再开新节点。这样可以保证平衡树的大小约等于当前序列的大小,因此开 5×105 5 × 10 5 即可。
  2. 由于插入的次数虽然少,但是插入的元素总数是很多的 (一次插入多个)。如果一个一个地插,会导致每次都要 O(logntot) O ( log ⁡ n ∗ t o t ) , 还带着splay这么大的常数, 4×106 4 × 10 6 的规模显然是无法承受的。
    解决办法:先在 O(tot) O ( t o t ) 的时间内把加入的那 tot t o t 个节点建出一棵新的完全BST,然后把 posi p o s i splay到根, posi+1 p o s i + 1 splay到根的右儿子,此时根的右儿子的左儿子为空,把新的平衡树挂到根的右儿子的左儿子上即可。同时注意内存回收池的使用。删除也是类似。删除的时候,首先把删除的节点一起放到根的右儿子的左儿子上,然后 O(tot) O ( t o t ) 地遍历这棵子树,把里面的节点 clear c l e a r 掉并放入内存回收池。
  3. 有个地方题面说的不明白: MAXSUM M A X − S U M 操作选出来的子列要非空。
    因此碰到了全是负数的整个序列,答案应该是绝对值最小的那个,而不是 0 0 .
    解决办法: 首先,正常节点的sum[0..3],val,tag都要设成 INF − I N F 而不是 0 0 . 根据splay常识,对区间[l,r]单独拎出来进行操作时我们先把 l1 l − 1 splay到根,再把 r+1 r + 1 splay到根的右儿子。因此为了避免 l1 l − 1 r+1 r + 1 合法,我们可以把要处理的区间平移一位变成 [2,n+1] [ 2 , n + 1 ] , 而 1 1 号点和(n+2)号点作为缓冲点。如果这两个点的 sum[0..3],val,tag s u m [ 0..3 ] , v a l , t a g 不慎设成了0, 则也会导致 MAXSUM M A X − S U M 无法处理答案为负(因为程序自动默认两个缓冲点是和最大的子列)。因此无论是正常点还是缓冲点都应该初值赋为 INF − I N F . (否则洛谷 90 90 分)
  4. 本题有个极坑之处, GETSUM G E T − S U M 操作的 tot t o t 可能为 0 0 !
    解决办法: 特判 (否则洛谷80分)
前四条是客观吐槽,后几条就是我自己犯的若干sb问题了
大概是写出了锅*7, 我好菜啊
  1. 建树时没有分清原数组中的下标和 splay s p l a y 中的编号。
    详见代码。build函数中的mid是原数组,pos是节点编号,而cfa,是父亲节点在原数组中的编号。(有点乱。。)
  2. REVERSE R E V E R S E 操作之后没有交换 sum[1] s u m [ 1 ] sum[2] s u m [ 2 ] pushup p u s h u p .
    由于我们维护的是最大子段和,如果左右子节点被交换,那么 sum[1] s u m [ 1 ] sum[2] s u m [ 2 ] 也随之交换。(可以认为节点的加法,即区间合并,不满足交换律)因此在 REVERSE R E V E R S E 打标记的同时应当交换两个儿子以及该节点的 sum[1] s u m [ 1 ] sum[2] s u m [ 2 ] , 并 pushup p u s h u p .同时,在pushdown时如果有 reverse r e v e r s e 标记,也要交换当前节点的 sum[1] s u m [ 1 ] sum[2] s u m [ 2 ]
  3. 为了偷懒减少代码长度, sum[0] s u m [ 0 ] 的合并少考虑了一种情况。(原地爆炸…以后再也不偷懒了呜呜呜)

好吧再多也没得说了,反正这道题尽管很毒瘤,但也是练习Splay的一道经典码农题,以后一定一定要抽空多写几遍!
怎么跑得这么慢啊...luogu不开O2要排后100了,bzoj开O2, 2137人AC我排1300多呜呜呜

代码实现
(luogu: 4399 ms without O2; bzoj: 5912 ms)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define llong long long
using namespace std;

const int SZ = 5e5;
const int N = 4e6;
const int INF = 6e8;
struct SplayNode
{
    int fa,son[2],tag,sum[4],sz,val;
    bool rev;
    SplayNode() {fa = son[0] = son[1] = rev = val = sz = 0; tag = sum[0] = sum[1] = sum[2] = sum[3] = -INF;}
    void clear() {fa = son[0] = son[1] = rev = val = sz = 0; tag = sum[0] = sum[1] = sum[2] = sum[3] = -INF;}
} spl[SZ+4],tmp[SZ+4];
int ids[N+4];
int id[SZ+4];
int a[SZ+4];
char opt[14];
int n,q,siz,rtn,tp;

int newnode()
{
    if(tp>0) {int ret = ids[tp]; ids[tp] = 0; tp--; return ret;}
    else {siz++; return siz;}
}

void pushup(int pos) //这里有简化很多的写法,推荐看洛谷题解
{
    if(pos==0) return;
    int ls = spl[pos].son[0],rs = spl[pos].son[1];
    if(ls==0 && rs==0) {spl[pos].sz = 1; spl[pos].sum[0] = spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[3]  = spl[pos].val; return;}
    if(ls==0 && rs!=0)
    {
        spl[pos].sz = spl[rs].sz+1;
        spl[pos].sum[3] = spl[rs].sum[3]+spl[pos].val;
        spl[pos].sum[2] = max(spl[pos].sum[3],spl[rs].sum[2]);
        spl[pos].sum[1] = max(spl[pos].sum[3],max(spl[pos].val,spl[pos].val+spl[rs].sum[1]));
        spl[pos].sum[0] = max(max(spl[pos].sum[1],spl[pos].sum[2]),spl[rs].sum[0]);
        return;
    }
    if(ls!=0 && rs==0)
    {
        spl[pos].sz = spl[ls].sz+1;
        spl[pos].sum[3] = spl[ls].sum[3]+spl[pos].val;
        spl[pos].sum[2] = max(spl[pos].sum[3],max(spl[pos].val,spl[pos].val+spl[ls].sum[2]));
        spl[pos].sum[1] = max(spl[pos].sum[3],spl[ls].sum[1]);
        spl[pos].sum[0] = max(max(spl[pos].sum[1],spl[pos].sum[2]),spl[ls].sum[0]);
        return;
    }
    spl[pos].sz = spl[ls].sz+spl[rs].sz+1;
    spl[pos].sum[3] = spl[ls].sum[3]+spl[pos].val+spl[rs].sum[3];
    spl[pos].sum[2] = max(max(spl[pos].sum[3],spl[rs].sum[2]),spl[rs].sum[3]+spl[pos].val+(spl[ls].sum[2]>0 ? spl[ls].sum[2] : 0));
    spl[pos].sum[1] = max(max(spl[pos].sum[3],spl[ls].sum[1]),spl[ls].sum[3]+spl[pos].val+(spl[rs].sum[1]>0 ? spl[rs].sum[1] : 0));
    spl[pos].sum[0] = max(max(max(spl[pos].sum[1],spl[pos].sum[2]),max(spl[ls].sum[0],spl[rs].sum[0])),max(max(spl[pos].val,spl[ls].sum[2]+spl[pos].val+spl[rs].sum[1]),max(spl[pos].val+spl[ls].sum[2],spl[pos].val+spl[rs].sum[1])));
}

void pushdown(int pos)
{
    if(pos==0) return;
    int ls = spl[pos].son[0],rs = spl[pos].son[1];
    if(ls==0 && rs==0) {spl[pos].tag = -INF; spl[pos].rev = 0; return;}
    if(spl[pos].tag>-INF)
    {
        if(ls!=0)
        {
            spl[ls].tag = spl[pos].tag; spl[ls].val = spl[ls].tag;
            spl[ls].sum[3] = spl[ls].tag*spl[ls].sz;
            spl[ls].sum[0] = spl[ls].sum[1] = spl[ls].sum[2] = spl[ls].tag>0 ? spl[ls].tag*spl[ls].sz : spl[ls].tag;
        }
        if(rs!=0)
        {
            spl[rs].tag = spl[pos].tag; spl[rs].val = spl[rs].tag;
            spl[rs].sum[3] = spl[rs].tag*spl[rs].sz;
            spl[rs].sum[0] = spl[rs].sum[1] = spl[rs].sum[2] = spl[rs].tag>0 ? spl[rs].tag*spl[rs].sz : spl[rs].tag;
        }
        spl[pos].tag = -INF;
    }
    if(spl[pos].rev==true)
    {
        if(ls!=0) {spl[ls].rev ^= 1; swap(spl[ls].son[0],spl[ls].son[1]); swap(spl[ls].sum[1],spl[ls].sum[2]);}
        if(rs!=0) {spl[rs].rev ^= 1; swap(spl[rs].son[0],spl[rs].son[1]); swap(spl[rs].sum[1],spl[rs].sum[2]);}
        spl[pos].rev = 0;
    }
}

void rotate(int x,bool dir)
{
    int y = spl[x].fa,z = spl[y].fa;
    pushdown(z); pushdown(y); pushdown(x);
    spl[x].fa = z;
    if(z>0)
    {
        if(spl[z].son[0]==y) spl[z].son[0] = x;
        else spl[z].son[1] = x;
    }
    spl[y].son[dir^1] = spl[x].son[dir];
    if(spl[x].son[dir]>0) spl[spl[x].son[dir]].fa = y;
    spl[x].son[dir] = y; spl[y].fa = x;
    pushup(y); pushup(x); pushup(z);
}

void splaynode(int x,int dest)
{
    while(spl[x].fa!=dest)
    {
        int y = spl[x].fa,z = spl[y].fa;
        if(z==dest)
        {
            if(spl[y].son[0]==x) rotate(x,1);
            else rotate(x,0);
        }
        else if(spl[z].son[0]==y)
        {
            if(spl[y].son[0]==x) {rotate(y,1); rotate(x,1);}
            else {rotate(x,0); rotate(x,1);}
        }
        else
        {
            if(spl[y].son[0]==x) {rotate(x,1); rotate(x,0);}
            else {rotate(y,0); rotate(x,0);}
        }
    }
    if(dest==0) rtn = x;
}

int ranktopos(int th)
{
    int pos = rtn;
    while(pos)
    {
        pushdown(pos);
        if(th<=spl[spl[pos].son[0]].sz) pos = spl[pos].son[0];
        else if(th==spl[spl[pos].son[0]].sz+1) {splaynode(pos,0); return pos;}
        else {th -= spl[spl[pos].son[0]].sz+1; pos = spl[pos].son[1];}
    }
    return 0;
}

void build(int lb,int rb,int cfa)
{
    if(lb>rb) return;
    int mid = (lb+rb)>>1; int pos = newnode(); id[mid] = pos;
    spl[pos].val = spl[pos].sum[0] = spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[3] = a[mid];
    spl[pos].fa = id[cfa];
    if(cfa>mid) spl[id[cfa]].son[0] = pos;
    else spl[id[cfa]].son[1] = pos;
    if(lb==rb) {spl[pos].sz = 1; return;}
    build(lb,mid-1,mid); build(mid+1,rb,mid);
    pushup(pos);
}

void inserttree(int x,int tot)
{
    int posx = ranktopos(x),posy = ranktopos(x+1);
    splaynode(posx,0); splaynode(posy,posx);
    int mid = (1+tot)>>1; int pos = id[mid];
    spl[posy].son[0] = pos; spl[pos].fa = posy;
    pushup(posy); pushup(posx);
}

void deletenode(int pos)
{
    if(spl[pos].son[0]) deletenode(spl[pos].son[0]);
    if(spl[pos].son[1]) deletenode(spl[pos].son[1]);
    tp++; ids[tp] = pos;
    spl[pos].clear(); 
}

void deletetree(int lb,int rb)
{
    int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
    splaynode(posl,0); splaynode(posr,posl);
    int pos = spl[posr].son[0];
    deletenode(pos);
    spl[posr].son[0] = 0;
    pushup(posr); pushup(posl);
}

void cover(int lb,int rb,int val)
{
    int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
    splaynode(posl,0); splaynode(posr,posl);
    int pos = spl[posr].son[0];
    spl[pos].tag = val; spl[pos].val = val;
    spl[pos].sum[3] = val*spl[pos].sz;
    spl[pos].sum[1] = spl[pos].sum[2] = spl[pos].sum[0] = val>0 ? val*spl[pos].sz : val;
    pushup(posr); pushup(posl);
}

void revint(int lb,int rb)
{
    int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
    splaynode(posl,0); splaynode(posr,posl);
    int pos = spl[posr].son[0];
    spl[pos].rev ^= 1; swap(spl[pos].son[0],spl[pos].son[1]); swap(spl[pos].sum[1],spl[pos].sum[2]);
    pushup(posr); pushup(posl);
}

int querysum(int lb,int rb)
{
    if(rb-lb<0) return 0;
    int posl = ranktopos(lb-1),posr = ranktopos(rb+1);
    splaynode(posl,0); splaynode(posr,posl);
    int pos = spl[posr].son[0];
    return spl[pos].sum[3];
}

int maxsum()
{
    return spl[rtn].sum[0];
}

int main()
{
    scanf("%d%d",&n,&q);
    for(int i=2; i<=n+1; i++) scanf("%d",&a[i]);
    a[1] = a[n+2] = -INF;
    build(1,n+2,0); rtn = id[(n+3)>>1];
    memset(id,0,sizeof(id));
    for(int i=1; i<=q; i++)
    {
        scanf("%s",opt);
        if(opt[0]=='I')
        {
            int x,tot; scanf("%d%d",&x,&tot);
            for(int j=1; j<=tot; j++) {scanf("%d",&a[j]); id[j] = 0;}
            build(1,tot,0);
            inserttree(x+1,tot);
        }
        else if(opt[0]=='D')
        {
            int x,tot; scanf("%d%d",&x,&tot);
            deletetree(x+1,x+tot);
        }
        else if(opt[0]=='M' && opt[2]=='K')
        {
            int x,tot,y; scanf("%d%d%d",&x,&tot,&y);
            cover(x+1,x+tot,y);
        }
        else if(opt[0]=='R')
        {
            int x,tot; scanf("%d%d",&x,&tot);
            revint(x+1,x+tot);
        }
        else if(opt[0]=='G')
        {
            int x,tot; scanf("%d%d",&x,&tot);
            printf("%d\n",querysum(x+1,x+tot));
        }
        else if(opt[0]=='M' && opt[2]=='X')
        {
            printf("%d\n",maxsum());
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值