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

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. 数学复习 ---- Mathematics Notes: A Programmer's Perspective ---- by Orzer ---- 我是沙茶

    今年是好没长进的一年呢..只学了些基本的方法.. 本文记号0] x:p x类型为p1] f(x) 表示一个函数2] (n_1,n_2,...) 表示多元组,特别的,(n)表示一个一元组3] x 表示一 ...

  2. Shortest Word Distance

    Given a list of words and two words word1 and word2, return the shortest distance between these two ...

  3. Trailing Zeros

    Write an algorithm which computes the number of trailing zeros in n factorial. Have you met this que ...

  4. (原创)Python文件与文件系统系列(5)——stat模块

    stat模块中定义了许多的常量和函数,可以帮助解释 os.stat().os.fstat().os.lstat()等函数返回的 st_result 类型的对象. 通常使用 os.path.is*() ...

  5. percona-xtrabackup备份mysql

    title: 1.percona-xtrabackup备份mysql date: 2016-04-10 23:19:12 tags: mysql categories: mysql --- 一.per ...

  6. 17.Python笔记之memcached&redis

    作者:刘耀 博客:www.liuyao.me 博客园:www.cnblogs.com/liu-yao 一.Memcached 1.介绍 Memcached 是一个高性能的分布式内存对象缓存系统,用于动 ...

  7. codeforces B. Semifinals 解题报告

    题目链接:http://codeforces.com/problemset/problem/378/B 题目意思:有n个参赛者,他们都需要参加两场半决赛.第一场半决赛的成绩依次是a1, a2, ... ...

  8. Linux下RPM、tar.gz、DEB格式软件包的区别

      初接解Linux的朋友一定对软件的安装特别头疼,同样都是for Linux,但RPM.tar.gz.DEB包还是有很大区别的,这种区别很有可能使你的安装过程进行不下去.那我们应该下载什么格式的包呢 ...

  9. linux下重启tomcat、实时查看tomcat运行日志

    在Linux系统下,重启Tomcat使用命令操作的! 首先,进入Tomcat下的bin目录 cd /usr/local/tomcat/bin 使用Tomcat关闭命令 ./shutdown.sh 查看 ...

  10. php 字符串处理

    <?php $a = " n001|n002|n003|n004 "; //echo strlen($a);//取字符串的长度 //var_dump(strcmp(" ...