[BZOJ1500][NOI2005]维修数列(Splay)
2016-11-09 22:00
477 查看
=== ===
这里放传送门=== ===
题解
这道题真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; }
偏偏在最后出现的补充说明
码大数据结构的时候要多想一想,不同的问题有不同的适用的写法,对于维护信息的设定十分关键,有时候会大大影响程序的运行速度。相关文章推荐
- [BZOJ1500]NOI2005 维修数列|splay
- 【bzoj1500】[NOI2005]维修数列 Splay
- BZOJ1500: [NOI2005]维修数列 [splay序列操作]【学习笔记】
- [NOI2005] [BZOJ1500] 维修数列 - splay
- BZOJ 1500: [NOI2005]维修数列 Splay
- BZOJ 1500|NOI 2005|维修数列|Splay
- [bzoj1500][NOI2005 维修数列] (splay区间操作)
- 【bzoj1500】[NOI2005]维修数列 Splay
- bzoj1500 [NOI2005]维修数列 splay
- BZOJ_1500_[NOI2005]维修数列_splay
- BZOJ 1500: [NOI2005]维修数列( splay )
- 【bzoj1500】[NOI2005]维修数列 Splay
- BZOJ1500: [NOI2005]维修数列 Splay
- [BZOJ 1500][NOI2005]维修数列(Splay)
- [Splay] BZOJ1500: [NOI2005]维修数列
- 【splay】[noi2005] bzoj1500 维修数列
- BZOJ1500 [NOI2005]维修数列(Splay)
- bzoj 1500 NOI2005 维修数列 [Splay]
- bzoj 1500: [NOI2005]维修数列 -- splay
- BZOJ 1500 [NOI2005]维修数列 Splay