splay求条:
#include <iostream>
using namespace std;
// 伸展树是一种二叉排序树,
// 它能在O(log n)内完成插入、查找和删除操作。
// 在伸展树上的一般操作都基于伸展操作:
// 假设想要对一个二叉查找树执行一系列的查找操作,
// 为了使整个查找时间更小,
// 被查频率高的那些条目就应当经常处于靠近树根的位置。
// 于是想到设计一个简单方法,
// 在每次查找之后对树进行重构,
// 把被查找的条目搬移到离树根近一些的地方。
// 伸展树应运而生。
// 伸展树是一种自调整形式的二叉查找树,
// 它会沿着从某个节点到树根之间的路径,
// 通过一系列的旋转把这个节点搬移到树根去。
const int N = 100005;
struct Node
{
int sz/*子树大小*/, val/*值*/, ch[2]/*子节点*/, cnt/*出现次数*/;
int father; // 父节点
};
Node tree[N];
int tot = 0, root;
bool get(int p) // 返回编号为p的节点是其父亲的左儿子(返回0)或者右儿子(返回1)。
{
return (tree[tree[p].father].ch[1] == p);
}
void push_up(int p) // 上传
{
if (p != 0)
{
tree[p].sz = 0;
if (tree[p].ch[0] != 0)
{
tree[p].sz += tree[tree[p].ch[0]].sz;
}
if (tree[p].ch[1] != 0)
{
tree[p].sz += tree[tree[p].ch[1]].sz;
}
tree[p].sz += tree[p].cnt;
}
}
void turn(int p) // 旋转,调换父子关系
{
/*
A
/ \
B C
/ \
D E
/ \
F G
*/
// 调换BD,同时保持排序树的性质
// 原数量关系:F<D<G<B<E<A<C
/* 结果:
A
/ \
D C
/ \
F B
/ \
G E
*/
// 数量关系:F<D<G<B<E<A<C
// 过程总结:
// 因为:
// 若是父亲左子节点,则父亲>自己,父亲在自己右子节点,自己原右子树也会比父亲小(左-中-右先遍历到),归左子树
// 若是父亲右子节点,则父亲<自己,父亲在自己左子节点,自己原左子树也会比父亲大(左-中-右后遍历到),归右子树
// 连接BG(父亲连自己的边连自己的反向儿子)
// 连接BD(调换父子关系,让父亲做反向儿子)
// 连接AD(自己与爷爷)
int fa = tree[p].father, grand_father = tree[fa].father;
int tmp = get(p);
tree[fa].ch[tmp] = tree[p].ch[!tmp];
tree[tree[fa].ch[tmp]].father = fa;
tree[p].ch[!tmp] = fa;
tree[fa].father = p;
if (grand_father != 0)
{
tree[p].father = grand_father;
if (tree[grand_father].ch[0] == fa)
tree[grand_father].ch[0] = p;
else tree[grand_father].ch[1] = p;
}
else tree[p].father = 0;
// 父亲已经成为儿子,先处理父亲
push_up(fa);
push_up(p);
}
void splay(int p) // 推到根,将访问多的放在上
{
// 不断turn
// 但当祖父,父亲,自己三点一线时,
// 一直旋转自己会把父亲、祖父等节点堆在与父连接的反向边上,
// 造成深度过深,算法退化
// 应当先turn父节点,交叉堆叠
while (tree[p].father != 0)
{
if (tree[tree[p].father].father == 0)
{
turn(p);
}
else
{
if (get(p) == get(tree[p].father))
{
turn(tree[p].father);
}
else turn(p);
}
}
root = p;
}
void insert(int v) // 插入v
{
if (root == 0) // 空树
{
++tot;
root = tot;
tree[root].ch[0] = tree[root].ch[1] = tree[root].father = 0;
tree[root].val = v;
tree[root].sz = tree[root].cnt = 1;
return ;
}
// 否则找该插入在哪里
int tmp = root;
while (1)
{
if (tree[tmp].val == v) // 找到原有的
{
++tree[tmp].cnt; // 计数器+1
push_up(tmp);
push_up(tree[tmp].father); // 更新自己与父亲(可能转父亲),在splay上去更新有关点
splay(tmp);
break;
}
int last = tmp;
if (v < tree[tmp].val)
tmp = tree[tmp].ch[0];
else tmp = tree[tmp].ch[1];
if (tmp == 0) // 找到最底下,直接插入
{
++tot;
tree[tot].ch[0] = tree[tot].ch[1] = 0;
tree[tot].val = v;
tree[tot].sz = tree[tot].cnt = 1;
tree[tot].father = last;
if (v < tree[last].val)
tree[last].ch[0] = tot;
else tree[last].ch[1] = tot;
push_up(last);
splay(tot); // 同上
break;
}
}
}
int find(int v) // 查找v的排名
{
// 找到自己,统计小于自己的个数+1
int ans = 0, now = root;
while (1)
{
if (v < tree[now].val)
{
// 左子树,统计不变
now = tree[now].ch[0];
}
else if (v >= tree[now].val) // 大于等于时,小于部分统计
{
// 右子树
if (tree[now].ch[0] != 0) // 统计左子树
ans += tree[tree[now].ch[0]].sz;
if (v == tree[now].val)
{
splay(now); // 推上去,下次方便
return ans+1;
}
else // 大于时,再统计根节点
ans += tree[now].cnt;
now = tree[now].ch[1];
}
}
}
int kth(int k) // 查询排名为k的点
{
int now = root;
while (1)
{
if (tree[now].ch[0] != 0 && k <= tree[tree[now].ch[0]].sz) // 左子树
{
now = tree[now].ch[0];
}
else // 根节点或右子树
{
int tmp = 0; // 到根节点的排名
if (tree[now].ch[0] != 0)
tmp += tree[tree[now].ch[0]].sz;
tmp += tree[now].cnt;
if (k <= tmp) // 在这区间
{
return tree[now].val;
}
else
{
k -= tmp;
now = tree[now].ch[1];
}
}
}
}
int pre() // 根的前驱
{
int now = tree[root].ch[0];
while (tree[now].ch[1] != 0)
{
now = tree[now].ch[1];
}
return now;
}
int nxt() // 根的后缀
{
int now = tree[root].ch[1];
while (tree[now].ch[0] != 0)
now = tree[now].ch[0];
return now;
}
void del(int v) // 删除v
{
// 1.旋到根
find(v); // 自动旋到根
// 2.分类
if (tree[root].cnt > 1)
{
tree[root].cnt--; // 不止一个,直接-1
push_up(root); // 更新
}
else if (tree[root].ch[0] == 0 && tree[root].ch[1] == 0) // 没有儿子,直接清空
{
tree[root].cnt = tree[root].sz = tree[root].val = tree[root].father = 0;
root = 0;
}
else if (tree[root].ch[0] == 0) // 无左儿子,用右儿子做根
{
int last = root;
root = tree[root].ch[1];
tree[root].father = 0;
tree[last].father = tree[last].ch[0] = tree[last].ch[1] = tree[last].val = tree[last].sz = tree[last].cnt = 0;
}
else if (tree[root].ch[1] == 0) // 同理
{
int last = root;
root = tree[root].ch[0];
tree[root].father = 0;
tree[last].father = tree[last].ch[0] = tree[last].ch[1] = tree[last].val = tree[last].sz = tree[last].cnt = 0;
}
else
{
// root有两个儿子,我们要拿root的前驱作新根,将原先root的右子树接到新root的右子树上
// (由于选的是前驱,原先root一定没有左子树)。
int last = root, thepre = pre();
splay(thepre); // 替换根
tree[tree[last].ch[1]].father = root; // 将右子树接上
tree[root].ch[1] = tree[last].ch[1];
tree[root].cnt = tree[root].sz = tree[root].val = tree[root].father = tree[root].ch[0] = tree[root].ch[1] = 0; // 清除
push_up(root); // 更新
}
}
signed main() // 主程序
{
ios::sync_with_stdio(false); // 加速读入
cin.tie(0), cout.tie(0); // 加速读入
int n; // 操作次数
cin >> n; // 读入数据
for(int i=1;i<=n;i++)
{
int op, x;
cin >> op >> x; // 输入操作的编号和操作数据
if(op == 1) insert(x); // 操作1:新建节点
if(op == 2) del(x); // 操作2:删除节点
if(op == 3)
{
insert(x); // 加入,防止其中没有
cout << find(x) << '\n'; // 操作3:查询x数的排名(排名定义为比当前数小的数的个数+1)
del(x); // 删除,防止影响正常操作
}
if(op == 4) cout << kth(x) << '\n'; // 操作4:查询排名为x的数
if(op == 5)
{
insert(x); // 加入,防止其中没有
cout << tree[pre()].val << '\n'; // 操作5:求x的前驱(前驱定义为小于x,且最大的数)
del(x); // 删除,防止影响正常操作
}
if(op == 6)
{
insert(x); // 加入,防止其中没有
cout << tree[nxt()].val << '\n'; // 操作6:求x的后继(后继定义为大于x,且最小的数)
del(x); // 删除,防止影响正常操作
}
}
return 0; // 结束程序
}
玄关。效果