Splay 伸展树
废话不说,有篇论文可供参考:杨思雨:《伸展树的基本操作与应用》
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 伸展树的更多相关文章
- Splay伸展树学习笔记
Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...
- 【学时总结】◆学时·VI◆ SPLAY伸展树
◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...
- [Splay伸展树]splay树入门级教程
首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...
- Splay伸展树入门(单点操作,区间维护)附例题模板
Pps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的自己总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是 ...
- Codeforces 675D Tree Construction Splay伸展树
链接:https://codeforces.com/problemset/problem/675/D 题意: 给一个二叉搜索树,一开始为空,不断插入数字,每次插入之后,询问他的父亲节点的权值 题解: ...
- UVA 11922 Permutation Transformer —— splay伸展树
题意:根据m条指令改变排列1 2 3 4 … n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘 ...
- [算法] 数据结构 splay(伸展树)解析
前言 splay学了已经很久了,只不过一直没有总结,鸽了好久来写一篇总结. 先介绍 splay:亦称伸展树,为二叉搜索树的一种,部分操作能在 \(O( \log n)\) 内完成,如插入.查找.删除. ...
- ZOJ3765---Lights (Splay伸展树)
Lights Time Limit: 8 Seconds Memory Limit: 131072 KB Now you have N lights in a line. Don't wor ...
- codeforces 38G - Queue splay伸展树
题目 https://codeforces.com/problemset/problem/38/G 题意: 一些人按顺序进入队列,每个人有两个属性,地位$A$和能力$C$ 每个人进入时都在队尾,并最多 ...
随机推荐
- 进入git diff 界面,无法继续输入命令
在终端,输入 git diff 文件名 命令之后回车,显示如下界面: 在网上查找,说输入q回车即可退出显示,执行,果然有效,输入h可以显示所有命令 命令如下: SUMMARY OF LESS COM ...
- 静态资源[org.springframework.web.servlet.PageNotFound]
springmvc 无法访问js.css.jpg等资源文件,tomcat启动报警告如下 [org.springframework.web.servlet.PageNotFound] - No mapp ...
- Kali Linux下破解WIFI密码挂载usb无线网卡的方法
Kali Linux下破解WIFI密码挂载usb无线网卡的方法 时间:2014-10-12 来源:服务器之家 投稿:root 首先我要说的是,wifi密码的破解不是想象中的那么容易,目前还 ...
- 基于nginx和uWSGI在Ubuntu上部署Django
转自: http://www.jianshu.com/p/e6ff4a28ab5a
- sharepoint添加应用程序
- 在cmd命令行中弹出Windows对话框
有时候用bat写一些小脚本最后会弹出对话框提示操作成功,可以用mshta.exe来实现,它是Windows系统的相关程序,用来执行.HTA文件,一般计算机上面都有这个程序,实现如下: mshta vb ...
- ts tp 高清播放软件 Elecard MPEG Player 6.0.130827
Elecard MPEG Player 6.0.130827 计算机配置不高的情况下,流畅播放高清视频. 缺点是搜索时停顿严重. 包里有注册机. 下载地址 http://pan.baidu.com/s ...
- codeforces A. K-Periodic Array 解题报告
题目链接:http://codeforces.com/problemset/problem/371/A 题目意思:给出n和k和一个只有1或者2组成的序列,需要求出最少的改变次数,使得 n/k 组里面的 ...
- Java 复制文件的高效方法
转载自:http://jingyan.baidu.com/article/ff4116259c2d7712e4823780.html 在Java编程中,复制文件的方法有很多,而且经常要用到.我以前一直 ...
- FZU 2140 Forever 0.5 (几何构造)
Forever 0.5 Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit ...