丑的一笔的普通平衡树ver的splay

80 篇文章 0 订阅

splay_Flaze ver1.0

常数大的飞起,代码丑得上天

BZOJ 3224 普通平衡树

CodeVS 4543 普通平衡树


从正式开始写平衡树到现在……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就是很开心_(:зゝ∠)_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值