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;
}
以上各种链接,侵删。