您的位置:首页 > 其它

[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;
}


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

码大数据结构的时候要多想一想,不同的问题有不同的适用的写法,对于维护信息的设定十分关键,有时候会大大影响程序的运行速度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  BZOJ NOI Splay 平衡树