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的时候都要下传标记
- 区间树:按初始位置为权值建一个Splay
- 动态插数:在第x个数后面插数就是按sz找到第x个数,转到根;再按sz找到第x+1个数转到根的右儿子;在x+1的左儿子插入新的数;在数列最前面插数要特判\
- 动态删数:删[l,r]的数,把l转到根,把r+1转到根的右儿子,把r+1的左儿子指针赋成0
- 区间覆盖为x:同上把l-1转到根、r+1转到右儿子,在r+1的左儿子上打上“覆盖为x”的标记,并将其数值改成x
- 区间翻转:同上把l-1、r+1转到位,把r+1的左儿子打上“翻转”的标记
- 区间求和:同上把l-1、r+1转到位,输出r+1左儿子的sum值
- 区间求最大子段和:类似线段树求区间最大子段和,维护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的节点
附题单
普通平衡树
区间树
- P2042 [NOI2005] 维护数列(不必立马完成)
- P4146 序列终结者
- P2596 [ZJOI2006]书架
- P3285 [SCOI2014]方伯伯的OJ(不必立马完成)