Luogu P3369 【模板】普通平衡树

Splay是个玄学玩意。
优点:每次查询,修改都会改变树的结构,使被查询频率高的条目更靠近树根,可以很方便,快速(?)的维护数列。
每种操作的时间复杂度是均摊O(logN)的,具体证明就不说了(其实是我不会233)

我觉得写的最好的题解,来自洛谷博客

实现细节:

  • 每次操作(无论是查询,修改)都要进行一次Splay(把当前节点旋转到根),否则就会出现奇怪的现象(TLE)。本人提交多次都在最后一个点TLE,后来才发现自己没有在第4个操作之后Splay。
  • 此题Splay单旋比双旋快,不过单旋无法保证复杂度,有可能无法把树变为随机形态,因此一般情况下用双旋比较好。例子请见这里
  • 可以用class封装,这样方便调用,也体现了程序的模块化(我会告诉你是为了更方便的写树套树吗233)
//我的馒头可是突破天际的啊
#include<cstdio>

const int MAXN = 100001;
const int INF = 0x7fffffff;

inline int read(){
	int k = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
	return k * f;
}

class BST
{
	#define root t[0].ch[1]
	public:
		void Insert(int v){ //新插入一个数 
//			point++;
			if(cnt == 0){ //当前树是空树 
				root = 1;
				init(v, 0);
				return;
			}
			int u = root;
			while(1){
				t[u].size++; //当前节点所在子树size+1 
				if(t[u].val == v){
					t[u].tag++;
					splay(u, root);
					return;
				}
				int son = v > t[u].val; //判断走左儿子还是右儿子 
				if(!t[u].ch[son]){
					init(v, u);
					t[u].ch[son] = cnt;
					splay(cnt, root);
					return;
				}
				u = t[u].ch[son];
			}
		}
		
		void Delete(int v){ //删除节点 
			find(v); 
			int u = root;
//			point--;
			if(t[u].tag > 1){
				t[u].size--;
				t[u].tag--;
				return;
			}
			
			if(!t[u].ch[0]){
				root = t[u].ch[1];
				t[root].fa = 0;
			}
			else{ //将v的前驱旋转到根节点的左儿子 
				int left = t[u].ch[0];
				while(t[left].ch[1])
					left = t[left].ch[1];
				splay(left, t[u].ch[0]);
				
				int right = t[u].ch[1];
				connect(right, left, 1); //删掉根节点,左右儿子重连 
				connect(left, 0, 1);
				update(left);
			}
			
			destroy(u);
		}
		
		int rank(int v){
			int ans = 0, u = root;
			while(1){
				if(t[u].val == v){
					ans += t[t[u].ch[0]].size;
					splay(u, root);
					return ans;
				}
//				if(u == 0)
//					return 0;
				if(v < t[u].val)
					u = t[u].ch[0];
				else{
					ans += t[t[u].ch[0]].size + t[u].tag;
					u = t[u].ch[1];
				}
			}
		}
		
		int kth(int x){
			int u = root;
			while(1){
				int lsum =  t[u].size - t[t[u].ch[1]].size; //左子树的总数量
				if(x > t[t[u].ch[0]].size && x <= lsum) break; //说明要查找的值就是该节点
				if(x < lsum) u = t[u].ch[0];
				else{
					x -= lsum;
					u = t[u].ch[1];
				}
			}
			splay(u, root);
			return t[u].val;
		}
		
		int upper(int v){
			int u = root;
			int ans = INF;
			while(u){
				if(t[u].val > v && t[u].val < ans) ans = t[u].val;
				if(v < t[u].val) u = t[u].ch[0];
				else u = t[u].ch[1];
			}
			return ans;
		}
		
		int lower(int x){
			int u = root;
			int ans = -INF;
			while(u){
				if(t[u].val < x && t[u].val > ans) ans = t[u].val;
				if(x > t[u].val) u = t[u].ch[1];
				else u = t[u].ch[0];
			}
			return ans;
		}
		
	private:
		struct node{
			int val, fa;
			int ch[2];
			int size, tag;
		}t[MAXN];
		
		int cnt, point; //cnt表示节点数量,point表示数字个数 
		
		inline void update(int x){
			t[x].size = t[t[x].ch[1]].size + t[t[x].ch[0]].size + t[x].tag;
		}
		
		inline int get(int x){ //获取节点与父亲关系 
			return t[t[x].fa].ch[1] == x;
		}
		
		inline void connect(int x, int f, int son){
			t[x].fa = f; t[f].ch[son] = x;
		}
		
		inline void rotate(int x){
			int y = t[x].fa, yson = get(x);
			int z = t[y].fa, zson = get(y);
			int T = t[x].ch[yson ^ 1];
			connect(T, y, yson);
			connect(y, x, (yson ^ 1));
			connect(x, z, zson);
			update(y), update(x); //y在x下 先更新y 
		}
		
		inline void splay(int at, int to){ //双旋,要分情况讨论 
			to = t[to].fa;
			while(t[at].fa != to){
				int up = t[at].fa;
				if(t[up].fa == to)
					rotate(at);
				else if(get(up) == get(at))
					rotate(up), rotate(at);
				else
					rotate(at), rotate(at);
			}
		}
		
		inline void init(int v, int fa){
			cnt++;
			t[cnt].val = v;
			t[cnt].fa = fa;
			t[cnt].tag = t[cnt].size = 1;
		}
		
		inline void destroy(int x){
			t[x].ch[0] = t[x].ch[1] = t[x].fa = t[x].size = t[x].tag = t[x].val = 0;
//			if(x == cnt) cnt--;
		}
		
		void find(int x){//找到一个点,然后旋转到root
			int u = root;
//			if(!u) return;
			while(t[u].ch[x > t[u].val] && t[u].val ^ x)
				u = t[u].ch[x > t[u].val];
			splay(u, root);
		}
	#undef root
}Splay;

int main(){
	freopen("in.txt", "r", stdin);
	int n = read();
	Splay.Insert(INF);
	Splay.Insert(-INF);
	while(n--){
		int opt = read(), x = read();
//		printf("opt = %d\n", opt);
		switch(opt){
			case 1: Splay.Insert(x); break;
			case 2: Splay.Delete(x); break;
			case 3: printf("%d\n", Splay.rank(x)); break;
			case 4: printf("%d\n", Splay.kth(x + 1)); break; //求第k+1大是因为之前插入了一个值为-INF的节点 
			case 5: printf("%d\n", Splay.lower(x)); break;
			case 6: printf("%d\n", Splay.upper(x)); break;
		}
//		printf("root = %d\n", );
	}
	return 0;
}

以上各种链接,侵删。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值