Splay及其简单应用总结

前言

说来惭愧,这么简单的东西我现在才算初步掌握,也是因为之前一直怕累没好好搞过数据结构。
先把上一周的学习和一些简单题总结下来,以备复习。
后人学习应该是不大可能,毕竟我现在水平还太弱了....

板子

基本学习的是yyb学长的。
在insert的地方稍作了修改,跟find的思路和代码结构更像,仅仅是方便自己记忆啦。
错误总结

insert之后要splay到根以更新sz
kth(0)会死循环,要在队首插元素时要特判(用于区间树)
然后就是板子了,比学长的要慢一点(13ms)

#include<bits/stdc++.h>
#define ri register int
#define ll long long
using namespace std;
const int maxn = 4e4 + 10;
struct T{
	int f,sz,cnt,val,ch[2];
}t[maxn];
int root,tot;
inline int rd(){
	int res = 0,f = 0;
	char ch = getchar();
	for(;!isdigit(ch);ch =  getchar()) if(ch == '-') f = 1;
	for(;isdigit(ch);ch = getchar()) res = (res<<3) + (res<<1) + ch - 48;
	return f ? -res : res;
}
inline void pushup(int x){
	t[x].sz = t[t[x].ch[0]].sz + t[t[x].ch[1]].sz + t[x].cnt;
}
inline void rotate(int x){
	int y = t[x].f,z = t[y].f,k = (t[y].ch[1] == x);
	t[z].ch[t[z].ch[1] == y] = x;
	t[x].f = z;
	t[y].ch[k] = t[x].ch[k^1];
	t[t[x].ch[k^1]].f = y;
	t[x].ch[k^1] = y;
	t[y].f = x;
	pushup(y); pushup(x);
}
inline void splay(int x,int goal){
	while(t[x].f != goal){
		int y = t[x].f,z = t[y].f;
		if(z != goal){
			if((t[y].ch[0] == x) ^ (t[z].ch[0] == y)) rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	if(!goal) root = x;
}
inline void find(int x){
	int u = root;
	if(!u) return;
	while(t[u].val != x && t[u].ch[x > t[u].val])
		u = t[u].ch[x > t[u].val];
	splay(u,0);
}
inline void insert(int x){
	int u = root;
	while(t[u].val != x && t[u].ch[x > t[u].val])
		u = t[u].ch[x > t[u].val];
	if(t[u].val == x){
		++t[u].cnt;
		splay(u,0);
		return;
	}
	t[++tot].val = x;
	t[tot].sz = 1;
	t[tot].cnt = 1;
	t[tot].f = u;
	t[u].ch[x > t[u].val] = tot;
	splay(tot,0);
}
inline int next(int x,int f){
	find(x);
	int u = root;
	if(t[u].val == x) return u;
	if(t[u].val < x && !f) return u;
	if(t[u].val > x && f) return u;
	u = t[u].ch[f];
	if(!u) return 0;
	while(t[u].ch[f^1]) u = t[u].ch[f^1];
	return u;
}
int main(){
	int n = rd();
	int x;
	ll ans;
	ans = x = rd();
	insert(x); --n;
	while(n--){
		x = rd();
		int pr = next(x,0),ne = next(x,1),del = 0x3f3f3f3f;
		if(pr) del = min(del,x - t[pr].val);
		if(ne) del = min(del,t[ne].val - x);
		ans += del;
		insert(x);
	}
	printf("%lld\n",ans);
	return 0;
}

简单题

P2042 [NOI2005] 维护数列

区间树(不是我起的!)集大成者。
需要维护一个序列,支持在某个位置插数、删除连续一段数、区间覆盖、区间翻转、区间求和、区间求最大连续子段和

除了区间覆盖都是特别经典的操作\

每次find的时候都要下传标记

  1. 区间树:按初始位置为权值建一个Splay
  2. 动态插数:在第x个数后面插数就是按sz找到第x个数,转到根;再按sz找到第x+1个数转到根的右儿子;在x+1的左儿子插入新的数;在数列最前面插数要特判\
  3. 动态删数:删[l,r]的数,把l转到根,把r+1转到根的右儿子,把r+1的左儿子指针赋成0
  4. 区间覆盖为x:同上把l-1转到根、r+1转到右儿子,在r+1的左儿子上打上“覆盖为x”的标记,并将其数值改成x
  5. 区间翻转:同上把l-1、r+1转到位,把r+1的左儿子打上“翻转”的标记
  6. 区间求和:同上把l-1、r+1转到位,输出r+1左儿子的sum值
  7. 区间求最大子段和:类似线段树求区间最大子段和,维护lmx,rmx,mx分别表示该点为根的子树所对应的区间中“从左边开头的”、“以右边结尾的”和“无限制的”区间最大子段和,类似DP进行更新即可

至此已经能在区间树上完成线段树的所有操作了(应该吧)
为什么我全都会胡了还没领经验呢?因为这道题要回收节点还要卡常....

P1110 [ZJOI2007]报表统计

维护一个序列,1.支持任意位置插入,2.求邻项绝对值之差的最小值,3.求任意两个数绝对值之差最小值

操作三:按数的大小维护一个Splay。每次加入一个x时求它的前驱和后继,分别求差的绝对值,并与当前答案比较、更新
操作二:维护一个区间树和维护当前所有邻项差的绝对值的可删除的小根堆。找到原本相邻且在插入位置前后的两个数,把它们的绝对值之差从堆中删除,将新的两个差加入堆中。
可删除的堆就用一个普通堆和一个删除堆来完成即可。
操作一:因为此题插入位置十分奇怪,要算插入的真实位置,然后按操作二、三那么维护就好了

还不太会的题

P5586 [P5350] 序列

维护一个区间,支持区间求和、区间翻转、区间赋值、区间加、区间复制([l1,r1]复制到[l2,r2])、区间交换([l1,r1]与[l2,r2]交换)

P3765 总统选举

套了个啥结论,等弄明白了再总结

P2596 [ZJOI2006]书架

在区间树中支持查编号为x的节点

附题单

普通平衡树

区间树

综合应用

posted @ 2021-05-17 21:49  Lumos壹玖贰壹  阅读(98)  评论(0编辑  收藏  举报