splay_Flaze ver1.0
常数大的飞起,代码丑得上天
从正式开始写平衡树到现在……Flaze码了无数遍rotate和splay(把节点转上去)【然而每次码完前两个之后就心灰意冷开始删档颓
于是今天花了一个晚自习终于过了普通平衡树感觉自己萌萌哒,尤其是回到教室一看发现竟然已经刷完了化学语文作业,瞬间上天x
嗯……调了这么久,大概手残是一部分,而且迷之感受到了指针的好……不管,反正我用伪指针我自豪【鉴于这个数据范围比较naive,Flaze并没有写空间回收站
又T又WA,吓得Flaze以为自己的splay被卡成了环,事实证明多码几遍rotate和splay还是有好处的23333
于是调得Flaze快要起飞的是这几个点↓
1. Flaze用的是闭区间,于是各种边界条件啥的手一抖就掉了一个等号,naive啊naive
2. Flaze才不说一不小心就用节点的key值当节点编号了,当时找到错瞬间觉得指针大法好,可惜已经退坑很久了
3. 讲道理Flaze真的是……splay了太多次23333,在找前驱后继的时候顺手一个splay,导致删除节点的时候一脸懵逼删错位了23333【原因的话看到Flaze是怎么删除节点的就自然懂了QuQ
呆马↓【讲道理第一次的呆马并不可能优美到哪里去x【毕竟Flaze是蒟蒻
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std; int n;
const int MAXN=100057;
struct t1{
int ch[2],fth,siz,cnt,key;
t1(){}
t1(int f,int k){
ch[0]=ch[1]=0;
siz=cnt=1;
fth=f,key=k;
}
}node[MAXN]; int cnt_node=0,root;
void updata(int x){
if(!x) return ;
int tmp=node[x].cnt;
if(node[x].ch[0]) tmp+=node[node[x].ch[0]].siz;
if(node[x].ch[1]) tmp+=node[node[x].ch[1]].siz;
node[x].siz=tmp;
}
//被小白科普的单变量平衡树,有一个好就是不管方向,感觉会比较优美?<span style="white-space:pre"> </span>注意0号空节点的判定,不然成环得嘿嘿嘿
void rotate(int b){
int a=node[b].fth;
int dir=b==node[a].ch[1];
node[a].ch[dir]=node[b].ch[dir^1];
node[b].ch[dir^1]=a;
int anc=node[a].fth;
if(anc)
node[anc].ch[node[anc].ch[1]==a]=b;
else root=b;
node[b].fth=anc;
node[a].fth=b;<span style="white-space:pre"> </span>//讲道理这句话Flaze经常忘orz
if(node[a].ch[dir]) node[node[a].ch[dir]].fth=a;
updata(a);
updata(b);
}
//把x节点转成tp的儿子
void splay(int x,int tp){
//puts("naive");
for(;;){
int fa=node[x].fth;
int anc=node[fa].fth;
if(fa==tp) return ;
if(anc==tp){<span style="white-space:pre"> </span>//这个也挺重要的
rotate(x);
return ;
}
int d1=fa==node[anc].ch[1],d2=x==node[fa].ch[1];
if(d1==d2)
rotate(fa),rotate(x);
else rotate(x),rotate(x);
}
}
//无比单纯的插入,最后记得splay
void insert(int k){
if(!root){
root=++cnt_node;
node[root]=t1(0,k);
return ;
}
int now=root;
for(;;){
if(node[now].key==k){
++node[now].cnt;
splay(now,0);
return ;
}
if(!node[now].ch[k>node[now].key]){
node[now].ch[k>node[now].key]=++cnt_node;
node[cnt_node]=t1(now,k);
splay(cnt_node,0);
return ;
}
now=node[now].ch[k>node[now].key];
}
}
//因为写的比较丑陋,特意判了能不能找到要求前驱后继的那个值,感觉自己十分的naive
int can_find(int x){
int now=root;
int taga=1;
for(;node[now].key!=x;now=node[now].ch[x>node[now].key])
if(!node[now].ch[x>node[now].key]){
taga=0;
break;
}
splay(now,0);
return taga;
}
//找到某个值的位置(保证树中存在值为x的节点
int find(int x){
int now=root;
for(;node[now].key!=x;now=node[now].ch[x>node[now].key]);
splay(now,0);
return now;
}
//找某个数的排名其实就是它的左儿子大小+1
int getrank(int x){
int pp=find(x);
return node[node[root].ch[0]].siz+1;
}
//找排名为x的数其实就是一直往下走,如果就是当前节点直接返回,然后大了往右小了往左,我这个可重是开了cnt于是……迷之丑陋2333
int findrank(int x){
for(int now=root;;){
if(node[node[now].ch[0]].siz<x&&x<=node[node[now].ch[0]].siz+node[now].cnt){
splay(now,0);
return node[now].key;
}
else;
if(node[node[now].ch[0]].siz>=x)
now=node[now].ch[0];
else if(x>node[node[now].ch[0]].siz+node[now].cnt){
x-=node[node[now].ch[0]].siz+node[now].cnt;
now=node[now].ch[1];
}
}
}
/*<span style="white-space:pre"> </span>这里是CYZ大爷的呆马……表示真的难为一只写指针的菊苣来调伪指针了23333【Flaze扑通扑通跪下来,多谢菊苣不杀(诶?)之恩
int findrank ( int x ) {
for ( int now = root ; ; ) {
const int Lsize = node [ node [ now ] . ch [ 0 ] ] . siz ;
if ( Lsize < x && Lsize + node [ now ] . cnt <= x ) break ;
const int d = Lsize + node [ now ] . cnt < x ;
if ( d ) x -= Lsize + node [ now ] . cnt ;
now = node [ now ] . ch [ d ] ;
}
splay ( now , 0 ) ;
return node [ now ] . key ;
}
*///讲道理要求前驱后继的话直接往下找,如果找得到当前节点就转到根findrank(当前节点左/右边一个),如果不存在这个数的话就看最后一个能够到达的节点,如果就是要求的东西就直接返回,否则就是求这个节点的前驱/后继
int node_pre(int x){
if(!can_find(x)){
if(node[root].key<x) return node[root].key;
}
int pp=node[node[root].ch[0]].siz;
return findrank(pp);
}
int node_nxt(int x){
if(!can_find(x))
if(node[root].key>x) return node[root].key;
int pp=node[node[root].ch[0]].siz+node[root].cnt+1;
return findrank(pp);
}
//取名废表示只好用了形象的名字来删除节点,其实就是把要删除的节点转到根,如果当前节点的值相同的不止一个数就直接--cnt,--siz,其他的只有一个儿子的话就直接把儿子变成根,有两个儿子的话就左儿子当根,右儿子转成左儿子的儿子,于是现在右儿子的左儿子讲道理只有要删除的那个节点,直接删掉就好了
void kill(int x){
x=find(x);
if(node[root].cnt>1){
--node[root].cnt;
--node[root].siz;
return ;
}
if(!node[root].ch[0]){
root=node[root].ch[1];
node[root].fth=0;
return ;
}
if(!node[root].ch[1]){
root=node[root].ch[0];
node[root].fth=0;
return ;
}
int rt=root; //Flaze表示这里出锅了调试了好久,直接用的求前驱后继,然而Flaze玩玩没想到啊,Splay的太多,xx和yy直接用的root结果根本就不是同一个值,竟然还跑了那么远,要不是手动单步调试(就是据某YJQ学长说听起来就像是在打某OJ的那个),根本没有发现如此神奇的坑,于是多设置一点变量好啊【或者说Flaze第一次写的splay有很多根本不需要的操作?以后慢慢改吧,反正今天过了普通平衡树简直神清气爽
int xx=find(node_pre(node[rt].key)),yy=find(node_nxt(node[rt].key));
splay(xx,0);
splay(yy,xx);
int now=node[root].ch[1];
node[now].ch[0]=0;
updata(now);
updata(root);
}
//下面这个是debug用的,感觉被CYZ和小白科普了好多东西啊……至少这样debug比Flaze原来的直接for 1到n好多了
void dfs(int x){
if(!x) return ;
printf ( "%d(" , node [ x ] . key ) ;
dfs ( node [ x ] . ch [ 0 ] ) ;
putchar ( ',' ) ;
dfs ( node [ x ] . ch [ 1 ] ) ;
putchar ( ')' ) ;
}
int main(){
scanf("%d",&n);
node[0]=t1(0,0);
node[0].siz=node[0].cnt=0;
while(n--){
int opt,x;
scanf("%d%d",&opt,&x);
if(opt==1) insert(x);
if(opt==2) kill(x);
if(opt==3) printf("%d\n",getrank(x));
if(opt==4) printf("%d\n",findrank(x));
if(opt==5) printf("%d\n",node_pre(x));
if(opt==6) printf("%d\n",node_nxt(x));
// dfs(root);
// cout<<endl<<"siz"<<node[root].siz<<endl;
// printf("root:%d siz:%d key:%d cnt:%d \n\n",root,node[root].siz,node[root].key,node[root].cnt);
}
return 0;
}
大概就是这样……??
表示Flaze当时看到各种操作简直吓得飞起来,小白一科普感觉智商得到了升华(嗯没错就是物理意义上的x),cyz的debug技巧简直赏心悦目啊(再也不用手跑建树了
Flaze不管Flaze就是很开心_(:зゝ∠)_