[BZOJ1500][NOI2005]维修数列(Splay)

=== ===

这里放传送门

=== ===

题解

这道题真TM难写啊这就是传说中的Splay板子题?ATP在2016SDSC的某天晚上从晚上9点写到11点码了出来,第二天上午调了一上午各种对拍各种T最后照着题解改了update的姿势才A掉。。。

前几个操作比较简单啦。第一个操作就把读进来的数字用build过程搞成一个Splay然后插进去就可以了。先找到第pos+1个数旋转到根,再找它的后继旋转到根的右儿子。这里有一句要提,ATP以前在Splay里面找前驱后继的方法是先把要找的那个点旋转到根,再找左子树中最靠右的儿子(前驱)和右子树中最靠左的儿子(后继)的,但是这样实际上常数奇大无比因为还带着一个Splay操作。后来ATP看算导的时候发现它在讲红黑树那一章讲了一个不需要Splay的找前驱后继的方法。。以找前驱为例,如果它有左儿子就找左子树最靠右的节点,否则就顺着父亲指针往上跳,设当前节点为v,找到第一个是它父亲右儿子的v,然后返回v的父亲即可。这样做时间复杂度也是树高级别的并且常数小得多。。注意在调用节点的儿子之前先push一下下放标记。(话说是不是只有愚蠢ATP和它愚蠢的小伙伴们用那种愚蠢的找前驱后继的方法= =)

第二个操作只要把那一坨子树揪出来扔到内存池里就行了,这里打了一个del标记,其实也不需要,只要每次从内存池里提取节点的时候看它下面有没有拖着子树,如果有的话先把子树分别塞到内存池里再把它提出来就可以了。三,四,五操作都是经典的平衡树操作,重点是第六个。

处理这一类问题的经典方法就是在节点上维护三个东西:靠着左边的最大值,靠着右边的最大值和全局最大值。这样update的时候就是左子树全局最大、右子树全局最大和左右子树拼起来的那一部分取一个max就可以了。但是在维护靠左最大lMax和靠右最大rMax的时候ATP出问题了。。一开始把这两个东西设定的太严格了,不允许它出现空串,这样的话在update的时候就特别麻烦,要分成各种情况讨论。。但是如果把限制放宽一点,允许这两个东西在序列全为负数的时候记录空串就能大大简化情况,并且能大大缩小update过程的常数,但是注意最后结果是不允许用空串的,所以全局最大值要特判一下。因为update过程调用特别频繁,所以它的常数会对程序整体运行时间造成特别大的影响。。一开始愚蠢的ATP把count过程写了40多行,结果大数据跑了1s+,改了以后大数据0.4s就过了。。。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 1000000000
#define inc(x)(x=(x%600000)+1)
using namespace std;
int n,m,a[500010],head,tail;
struct Node{
    Node *ch[2],*fa;
    int size,sum,dlt,data,lMax,rMax,tMax;
    bool rev,del;
    Node();
    bool pl(){return this==fa->ch[1];}
    void Rever(){swap(ch[0],ch[1]);swap(lMax,rMax);rev^=1;}
    void Cover(int v);
    void count();
    void push();
    Node *Nxt();
    void print();
}*null,*Root,Pool[600010],*q[600010];
Node::Node(){
    ch[1]=ch[0]=fa=null;rev=del=false;
    size=sum=data=lMax=rMax=0;
    dlt=inf;tMax=-inf;
}
void Del(Node *k){inc(tail);q[tail]=k;k->del=true;}
Node *New(Node *f,int d){
    inc(head);
    if (q[head]->ch[0]!=null) Del(q[head]->ch[0]);
    if (q[head]->ch[1]!=null) Del(q[head]->ch[1]);
    *q[head]=Node();
    Node *k=q[head];
    k->fa=f;k->data=k->sum=k->tMax=d;
    k->size=1;
    k->lMax=k->rMax=(d<0)?0:d;
    return k;
}
void Node::Cover(int v){
    sum=v*size;data=dlt=v;
    if (v<0){lMax=rMax=0;tMax=v;}//对lMax和rMax的设定不能太严格
    else lMax=rMax=tMax=v*size;//分成正数和负数两种情况讨论
}
void Node::count(){
    size=ch[0]->size+ch[1]->size+1;
    sum=ch[1]->sum+ch[0]->sum+data;
    lMax=max(ch[0]->lMax,ch[0]->sum+data+ch[1]->lMax);
    rMax=max(ch[1]->rMax,ch[1]->sum+data+ch[0]->rMax);
    tMax=max(ch[0]->tMax,ch[1]->tMax);
    tMax=max(tMax,ch[0]->rMax+data+ch[1]->lMax);
}
void Node::push(){
    if (rev==true){
        if (ch[0]!=null) ch[0]->Rever();
        if (ch[1]!=null) ch[1]->Rever();
        rev=false;
    }
    if (dlt!=inf){
        if (ch[0]!=null) ch[0]->Cover(dlt);
        if (ch[1]!=null) ch[1]->Cover(dlt);
        dlt=inf;
    }
}
Node* Node::Nxt(){
    push();
    if (ch[1]!=null){
        Node *k=ch[1];
        while (k->ch[0]!=null){
            k->push();k=k->ch[0];
        }//注意这里随时push一下
        return k;
    }else{
        Node *k=this;//处理没有右儿子的情况
        while (k->pl()==1){k->count();k=k->fa;}
        return k->fa;
    }
}
void Rotate(Node *k){
    Node *r=k->fa;
    if (r==null||k==null) return;
    int x=k->pl()^1;
    r->push();k->push();
    r->ch[x^1]=k->ch[x];
    if (k->ch[x]!=null)
      r->ch[x^1]->fa=r;
    if (r->fa==null) Root=k;
    else r->fa->ch[r->pl()]=k;
    k->fa=r->fa;r->fa=k;
    k->ch[x]=r;
    r->count();
    k->count();
}
void Splay(Node *r,Node *tar){
    for (;r->fa!=tar;Rotate(r))
      if (r->fa->fa!=tar)
        Rotate(r->pl()==r->fa->pl()?r->fa:r);
}
Node *build(int l,int r){
    if (l>r) return null;
    int mid=(l+r)>>1;
    Node *k=New(null,a[mid]);
    k->ch[0]=build(l,mid-1);
    k->ch[1]=build(mid+1,r);
    if (k->ch[0]!=null) k->ch[0]->fa=k;
    if (k->ch[1]!=null) k->ch[1]->fa=k;
    k->count();return k;
}
void Find(int k,Node *tar){
    Node *r=Root;
    r->push();r->count();
    while (k!=r->ch[0]->size+1){
        if (k<r->ch[0]->size+1) r=r->ch[0];
        else{
            k=k-r->ch[0]->size-1;r=r->ch[1];
        }
        r->push();r->count();
    }
    Splay(r,tar);
}
void Insert(int pos,int tot){
    Node *k;
    Find(pos+1,null);
    k=Root->Nxt();//在根节点的右儿子的左儿子处给插入数空出位置
    Splay(k,Root);
    for (int i=1;i<=tot;i++) scanf("%d",&a[i]);
    k=build(1,tot);//把读进来的树建成一棵Splay
    Root->ch[1]->ch[0]=k;
    k->fa=Root->ch[1];
    Splay(k,null);
}
void Delete(int pos,int tot){
    Find(pos,null);
    Find(pos+tot+1,Root);//提取区间然后删除
    Del(Root->ch[1]->ch[0]);
    Root->ch[1]->ch[0]=null;
    Root->ch[1]->count();
    Root->count();//注意及时更新
}
void MakeSame(int pos,int tot,int c){
    Find(pos,null);
    Find(pos+tot+1,Root);
    Root->ch[1]->ch[0]->Cover(c);
    Root->ch[1]->count();
    Root->count();
}
void Reverse(int pos,int tot){
    Find(pos,null);
    Find(pos+tot+1,Root);
    Root->ch[1]->ch[0]->Rever();
    Splay(Root->ch[1]->ch[0],null);
}
int GetSum(int pos,int tot){
    Find(pos,null);
    Find(pos+tot+1,Root);
    return Root->ch[1]->ch[0]->sum;
}
int main()
{
    null=new Node;*null=Node();
    head=0;tail=600000;
    for (int i=0;i<=600000;i++){
        Pool[i]=Node();q[i]=Pool+i;
    }
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    a[0]=a[n+1]=-inf;
    Root=build(0,n+1);
    for (int i=1;i<=m;i++){
        char opt[20];
        scanf("%s",opt);
        switch (opt[0]){
            case 'I':{
                int pos,tot;
                scanf("%d%d",&pos,&tot);
                Insert(pos,tot);
                break;
            }
            case 'D':{
                int pos,tot;
                scanf("%d%d",&pos,&tot);
                Delete(pos,tot);
                break;
            }
            case 'M':{
                if (opt[2]=='K'){
                    int pos,tot,c;
                    scanf("%d%d%d",&pos,&tot,&c);
                    MakeSame(pos,tot,c);
                }else printf("%d\n",Root->tMax);
                break;
            }
            case 'R':{
                int pos,tot;
                scanf("%d%d",&pos,&tot);
                Reverse(pos,tot);
                break;
            }
            case 'G':{
                int pos,tot;
                scanf("%d%d",&pos,&tot);
                printf("%d\n",GetSum(pos,tot));
                break;
            }
        }
    }
    return 0;
}

偏偏在最后出现的补充说明

码大数据结构的时候要多想一想,不同的问题有不同的适用的写法,对于维护信息的设定十分关键,有时候会大大影响程序的运行速度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值