废话不说,有篇论文可供参考:杨思雨:《伸展树的基本操作与应用》

Splay的好处可以快速分裂和合并。

===============================14.07.26更新=============================

实在看不惯那充满bug的指针树了!动不动就re!动不动就re!调试调个老半天,谁有好的调试技巧为T_T

好不容易写了个模板splay出来,指针的,好写,核心代码rotate和splay能压缩到10行。

#include <cstdio>
using namespace std; class Splay {
public:
//这个版本的splay会更改null的fa和ch,但是对结果没影响,这点要牢记(其实处理好rot里的fa->fa->setc那里就行了,也就是特判一下)
struct node {
node* ch[2], *fa;
int key, size;
node() { ch[0]=ch[1]=fa=0; key=size=0; }
void pushup() { size=ch[0]->size+ch[1]->size+1; }
bool d() { return fa->ch[1]==this; }
void setc(node* c, bool d) { ch[d]=c; c->fa=this; }
}*null, * root;
Splay() {
null=new node;
null->ch[0]=null->ch[1]=null->fa=null;
root=null;
}
void rot(node* rt) {
node* fa=rt->fa; bool d=rt->d();
fa->fa->setc(rt, fa->d()); //这里要注意,因为d()返回的是路径,所以不要高反了。
fa->setc(rt->ch[!d], d);
rt->setc(fa, !d);
fa->pushup();
if(root==fa) root=rt;
}
node* newnode(const int &key) {
node* ret=new node();
ret->key=key; ret->size=1;
ret->ch[0]=ret->ch[1]=ret->fa=null;
return ret;
}
void splay(node* rt, node* fa) {
while(rt->fa!=fa)
if(rt->fa->fa==fa) rot(rt);
else rt->d()==rt->fa->d()?(rot(rt->fa), rot(rt)):(rot(rt), rot(rt));
rt->pushup();
}
void insert(const int &key) {
if(root==null) { root=newnode(key); return; }
node* t=root;
while(t->ch[key>t->key]!=null) t=t->ch[key>t->key];
node* c=newnode(key);
t->setc(c, key>t->key);
t->pushup();
splay(c, null);
}
void remove(const int &key) {
//remove是各种坑,各种注意细节判断null
node* t=root;
while(t!=null && t->key!=key) t=t->ch[key>t->key];
if(t==null) return;
splay(t, null);
node* rt=root->ch[0];
if(rt==null) rt=root->ch[1];
else {
node* m=rt->ch[1];
while(m!=null && m->ch[1]!=null) m=m->ch[1];
if(m!=null) splay(m, root);
rt=root->ch[0];
rt->setc(root->ch[1], 1);
}
delete root;
root=rt;
root->fa=null;
if(root!=null) root->pushup(); //这里一定要注意,因为咱这是会修改null的孩子的!!
}
int rank(const int &key, node* rt) {
if(rt==null) return 0;
int s=rt->ch[0]->size+1;
if(key==rt->key) return s;
if(key>rt->key) return s+rank(key, rt->ch[1]);
else return rank(key, rt->ch[0]);
}
node* select(const int &k, node* rt) {
if(rt==null) return null;
int s=rt->ch[0]->size+1;
if(s==k) return rt;
if(k>s) return select(k-s, rt->ch[1]);
else return select(k, rt->ch[0]);
}
}; int main() { return 0;
}

==============================================================

上代码(指针版,可能有潜在bug)

#include <iostream>
#include <string>
using namespace std; #define F(rt) rt-> pa
#define K(rt) rt-> key
#define CH(rt, d) rt-> ch[d]
#define C(rt, d) (K(rt) > d ? 0 : 1)
#define NEW(d) new Splay(d)
#define PRE(rt) F(rt) = CH(rt, 0) = CH(rt, 1) = null struct Splay {
Splay* ch[2], *pa;
int key;
Splay(int d = 0) : key(d) { ch[0] = ch[1] = pa = NULL; }
}; typedef Splay* tree;
tree null = new Splay, root = null; void rot(tree& rt, int d) {
tree k = CH(rt, d^1), u = F(rt); int flag = CH(u, 1) == rt;
CH(rt, d^1) = CH(k, d); if(CH(k, d) != null) F(CH(k, d)) = rt;
CH(k, d) = rt; F(rt) = k; rt = k; F(rt) = u;
if(u != null) CH(u, flag) = k; //这里不能等于rt,否则会死的
} void splay(tree nod, tree& rt) {
if(nod == null) return;
tree pa = F(rt); //要先记录rt的父亲节点,因为rt会变的,所以判断条件里不能有rt
while(F(nod) != pa) {
if(F(nod) == rt)
rot(rt, CH(rt, 0) == nod); //当这步完后,rt会改变
else {
int d = CH(F(F(nod)), 0) == F(nod); //记录nod父亲是nod父亲的父亲的哪个儿子,1为左儿子,0为右儿子
int d2 = CH(F(nod), 0) == nod; //同上
if(d == d2) { rot(F(F(nod)), d); rot(F(nod), d2); } //当路线相同时,先转父亲的父亲,后转父亲
else { rot(F(nod), d2); rot(F(nod), d); } //当路线不同时,先转父亲,再转父亲的父亲 (在这里,第一次转后,F(nod)就是父亲的父亲了,因为第一次转后,nod改变了啦。)
}
}
rt = nod; //在这里还要重新赋值给引用的指针,不然照样会死
} tree maxmin(tree rt, int d) { //d=0时找最小,d=1时找最大
if(rt == null) return null;
while(CH(rt, d) != null) rt = CH(rt, d);
return rt;
} tree ps(tree rt, int d) { //d=0时找前驱,d=1时找后继
if(rt == null) return null;
rt = CH(rt, d);
return maxmin(rt, d^1);
} tree search(tree& rt, int d) {
if(rt == null) return null;
tree t = rt;
while(t != null && K(t) != d) t = CH(t, C(t, d));
splay(t, rt); //搜索到记得splay
return t;
} void insert(tree& rt, int d) {
tree q = NULL, t = rt;
while(t != null) q = t, t = CH(t, C(t, d));
t = new Splay(d);
PRE(t); //初始化他的3个指针
if(q) F(t) = q, CH(q, C(q, d)) = t; //如果他有父亲,那么就要初始化他的父亲指针和他父亲的儿子指针
else rt = t; //如果没有,那么t就是以rt为根的这棵树的根
splay(t, rt);
} void del(tree& rt, int d) {
if(search(rt, d) == null) return; //搜索的这一次,如果有就顺便将他splay到根了,所以下面不需要再splay
tree t = rt; //这个根就是要删除的
if(CH(t, 0) == null) t = CH(rt, 1); //如果没有左子女,就直接赋值喽
else { //如果有,就要将树的根设置为左子女最大元素,然后将右子树赋值为新树的右子女
t = CH(rt, 0); //t就是新根
splay(ps(rt, 0), t); //找到旧根的后继(其实就是新根的最大值)并设置为新树的根
CH(t, 1) = CH(rt, 1); //然后将右子树设置为原根的右子树
if(CH(rt, 1) != null) F(CH(rt, 1)) = t; //在这里,如果有这个右子树,还要将右子树的父亲指针设置为新根,否则又要挂
}
delete rt; //删除
F(t) = null; //设置新根的父亲指针
rt = t;
} void out(string str) {
cout << str;
} int main() {
out("1: insert\n2: del\n3: search\n4: max\n5: min\n");
PRE(null); //一定记得初始化null指针的其它指针,使他们全部指向自己
int c, t;
tree a;
while(cin >> c) {
switch(c) {
case 1: cin >> t;
insert(root, t);
break;
case 2: cin >> t;
del(root, t);
break;
case 3: cin >> t;
if(search(root, t) == null) out("Not here\n");
else out("Is here!\n");
break;
case 4: a = maxmin(root, 1);
if(a != null) cout << a-> key << endl;
else out("Warn!\n");
break;
case 5: a = maxmin(root, 0);
if(a != null) cout << a-> key << endl;
else out("Warn!\n");
break;
default:
break;
}
} return 0;
}

  

另写了一个数组的,比指针的好写多了,而且很精简。

自底向上的splay,还没有自带回收内存,支持区间修改,区间求和的模板。(是http://www.wikioi.com/problem/1082/的题)

#include <cstdio>
using namespace std; #define K(x) key[x]
#define S(x) size[x]
#define C(x, d) ch[x][d]
#define F(x) fa[x]
#define L(x) ch[x][0]
#define R(x) ch[x][1]
#define keytree L(R(root))
#define LL long long const int maxn = 222222;
int size[maxn], key[maxn], fa[maxn], ch[maxn][2], add[maxn];
LL sum[maxn];
int tot, root;
int arr[maxn]; void newnode(int& x, int k, int f) {
x = ++tot; F(x) = f; S(x) = 1;
K(x) = sum[x] = k;
} void pushup(int x) {
S(x) = S(R(x)) + S(L(x)) + 1;
sum[x] = sum[R(x)] + sum[L(x)] + K(x) + add[x];
} void pushdown(int x) {
if(add[x]) {
K(x) += add[x];
add[R(x)] += add[x];
add[L(x)] += add[x];
sum[R(x)] += (LL)add[x] * (LL)S(R(x));
sum[L(x)] += (LL)add[x] * (LL)S(L(x));
add[x] = 0;
}
} void rot(int x, int c) {
int y = F(x);
pushdown(y); pushdown(x);
C(y, !c) = C(x, c); F(C(x, c)) = y;
C(x, c) = y; F(x) = F(y); F(y) = x;
if(F(x)) C(F(x), R(F(x)) == y) = x;
pushup(y);
} void splay(int x, int y) {
if(!x) return;
pushdown(x);
while(F(x) != y) {
if(F(F(x)) == y) rot(x, L(F(x)) == x);
else {
int d1 = L(F(F(x))) == F(x);
int d2 = L(F(x)) == x;
if(d1 == d2) { rot(F(x), d1); rot(x, d2); }
else { rot(x, d2); rot(x, d1); }
}
}
pushup(x);
if(!y) root = x;
} void insert(int k) {
int x = root;
while(C(x, k > K(x))) x = C(x, k > K(x));
newnode(C(x, k > K(x)), k, x);
splay(C(x, k > K(x)), 0);
} int sel(int k, int x) {
for(pushdown(x); S(L(x))+1 != k; pushdown(x))
if(k <= S(L(x))) x = L(x);
else k -= (S(L(x))+1), x = R(x);
return x;
} //特定的select,因为多插了2个边界节点,所以和原版的select有区别
int vsel(int k, int x) {
for(pushdown(x); S(L(x)) != k; pushdown(x))
if(k < S(L(x))) x = L(x);
else k -= (S(L(x)) + 1), x = R(x);
return x;
} void build(int l, int r, int& rt, int f) {
if(l > r) return;
int mid = (l+r) >> 1;
newnode(rt, arr[mid], f);
build(l, mid-1, L(rt), rt); build(mid+1, r, R(rt), rt);
pushup(rt);
} void query() {
int l, r;
scanf("%d%d", &l, &r);
splay(vsel(l-1, root), 0);
splay(vsel(r+1, root), root);
printf("%lld\n", sum[keytree]);
} void updata() {
int l, r, _add;
scanf("%d%d%d", &l, &r, &_add);
splay(vsel(l-1, root), 0);
splay(vsel(r+1, root), root);
sum[keytree] += (LL)_add * (LL)S(keytree);
add[keytree] += (LL)_add;
} int n, q, t; void init() {
for(int i = 1; i <= n; ++i) scanf("%d", &arr[i]);
newnode(root, -1, 0);
newnode(R(root), -1, root);
S(root) = 2;
build(1, n, keytree, R(root));
pushup(R(root));
pushup(root);
} int main() {
scanf("%d", &n);
init();
scanf("%d", &q);
while(q--) {
scanf("%d", &t);
if(t == 1) updata();
else query();
}
return 0;
}

  

Splay 伸展树的更多相关文章

  1. Splay伸展树学习笔记

    Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...

  2. 【学时总结】◆学时·VI◆ SPLAY伸展树

    ◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...

  3. [Splay伸展树]splay树入门级教程

    首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...

  4. Splay伸展树入门(单点操作,区间维护)附例题模板

    Pps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的自己总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是 ...

  5. Codeforces 675D Tree Construction Splay伸展树

    链接:https://codeforces.com/problemset/problem/675/D 题意: 给一个二叉搜索树,一开始为空,不断插入数字,每次插入之后,询问他的父亲节点的权值 题解: ...

  6. UVA 11922 Permutation Transformer —— splay伸展树

    题意:根据m条指令改变排列1 2 3 4 … n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘 ...

  7. [算法] 数据结构 splay(伸展树)解析

    前言 splay学了已经很久了,只不过一直没有总结,鸽了好久来写一篇总结. 先介绍 splay:亦称伸展树,为二叉搜索树的一种,部分操作能在 \(O( \log n)\) 内完成,如插入.查找.删除. ...

  8. ZOJ3765---Lights (Splay伸展树)

    Lights Time Limit: 8 Seconds      Memory Limit: 131072 KB Now you have N lights in a line. Don't wor ...

  9. codeforces 38G - Queue splay伸展树

    题目 https://codeforces.com/problemset/problem/38/G 题意: 一些人按顺序进入队列,每个人有两个属性,地位$A$和能力$C$ 每个人进入时都在队尾,并最多 ...

随机推荐

  1. HDU 1708 简单dp问题 Fibonacci String

    Fibonacci String Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  2. PHP随笔

    php(PHP开发) PHP(外文名: Hypertext Preprocessor,中文名:“超文本预处理器”)是一种通用开源脚本语言.语法吸收了C语言.Java和Perl的特点,易于学习,使用广泛 ...

  3. c# 继承,多态,new /overrid 区别, 引用父类的方法

    好久没碰c#了,偶尔需要制作点小工具.为了一个灵活的架构设计,需要对继承/多态有比较深刻的理解. 不料忘得差不多了,好吧,再来回忆下.直接上代码了,如下: using System; using Sy ...

  4. PHP 冒泡原理

    header('Content-Type: text/html; charset=utf-8'); // 简单冒泡算法 $a = array(5,43,3,2,1); function mp($a){ ...

  5. ajax:post 400错误

    POST http://localhost:8080/purchase/purchase-apply/update.htm 400 (Bad Request) n.ajaxTransport.k.co ...

  6. Android Studio项目引入外部库注意事项(PullToRefresh)

    Android Studio开发App项目时引入第三方库是个比较麻烦的事情.之前导入Volley就折腾了好久,导入下拉刷新控件PullToRefresh时又碰到了各种问题.在此记录一下,以便查阅. 一 ...

  7. 用RPM包安装MySQL的默认安装路径问题

    在安装PHP时候要对一些配置选项进行设置,其中就有:--with-mysql[=DIR]:包含MySQL扩展,[=DIR]指定mysql安装目录,省略[=DIR]则为默认位置/usr--with-my ...

  8. jQuery结合Ajax实现简单的前端验证和服务端查询

    上篇文章写了简单的前端验证由传统的JavaScript转向流畅的jQuery滑动验证,现在拓展一下,使用Ajax实现用户体验比较好的异步查询,同样还是从建立一个简单的表单开始 <form nam ...

  9. July 31st, Week 32nd Sunday, 2016

    If you wept for the missing sunset, you would miss all the shining stars. 如果你为错过夕阳而哭泣,那你有可能也会错过灿烂的星空 ...

  10. 记录远程桌面登录者的IP和MAC

    WINDOWS 2003 远程桌面不能记录登陆IP真是件头痛的事,本方法可以记录登陆者IP,具体的操作步骤如下: 1.建立一个存放日志的目录,如C盘下建立一个RDP的目录“C:/RDP”. 2.然后在 ...