luogu3369 【模板】 普通平衡树 Splay
题目大意
维护一个数据结构,满足以下操作:
- 插入x数
- 删除x数(若有多个相同的数,因只删除一个)
- 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
- 查询排名为x的数
- 求x的前驱(前驱定义为小于x,且最大的数)
- 求x的后继(后继定义为大于x,且最小的数)
引子
维护一个二叉搜索树,其中每一个节点满足左节点的key小于该节点的key小于等于右节点的key。由于本题要求排名,所以节点中要有值Size表示子树的大小。由于本题要求若有多个相同的数,输出最小的排名,因此每个节点还要维护一个值count表示节点的数值重复了多少次。
但是如果插入的key值是按顺序排列的,整棵树就退化成了一条链,那就没意义了。所以我们用到了Splay。
各个函数解释
Rotate(易错点)
如果我们要Rotate(Node *cur),就是要达到这样的效果(假设cur为cur->Father->LeftSon):cur->RightSon到了原先cur->Father->LeftSon的位置(位置faSon),cur->Father到了原先cur->RightSon的位置(位置curSon),cur到了原先cur->Father的位置(位置gfaSon)(这样仍然满足二叉树的数值大小要求)。(如果cur为cur->Father->RightSon则上述内容左右相反。)最后,由于原先cur->Father和cur的子节点变了,所以还要通过一个Refresh函数更新那两个节点的size。
但是由于cur既可能是父亲的左孩子也可能是父亲的右孩子,所以我们根据left和right的不同写两个不同的函数?不用。我们可以用指向节点指针的指针来代表一个节点内存储的不同指针。以后对这个指针的指针操作,就是对原节点内的不同指针的操作。用这种方式表示上文中的三个位置(如果gfa是个空,则gfaSon这个指针的指针就指向Root这个节点的指针,这样就能在Splay中自动对树根进行操作了)。这样可以简化代码。
上图中,黑色部分表示原树,灰色箭头表示指针的指针,灰色圈表示某个情况下指针的指针的值(节点的指针)具体是多少。红色箭头表示操作。
Splay
splay(Node *cur)是要通过旋转操作将节点cur放到顶端。具体操作为:如果节点cur, cur->Father, cur->Father->Father三个节点共线,则先转fa,后转cur,(如果直接只转cur,拿一条链试试,树的最大深度没变)否则只转cur。在插入节点和查找节点时都要进行Splay操作。
Insert
Insert(int key)是要将key插入到树中。首先用二叉树搜索操作查找key值可能存在的节点。如果已经存在key值相等的节点,则将该节点的count值加一即可,否则就要新建一个节点。利用节点指针的指针,我们的节点的指针的指针cur就表示了它到底是父亲的左孩子还是右孩子。
最后要将新结点splay上去。
GetPrevNode
GetPrevNode(Node *cur)是要找到key值比cur小的key值当中最大的key值所对应的结点。找到cur的左孩子,然后不断转移到自己的右孩子去。返回最终的结点。
GetNextNode正好相反。
Find
Find(int key)是要找到key值对应的结点。二叉搜索树的基本操作。
最后要将搜索到的结点Splay上去。
Delete(易错点)
Delete(int key)是要将key值对应结点删除。首先用Find函数找到那个结点。此时,因为Find函数调用了Splay,所以当前结点必然在树顶。此时分情况讨论:
- cur结点count值大于1,则将count值--即可。
- cur只有不多于1个子节点。则将cur存在的那个子节点(如果cur没有子节点,则为NULL)设为树根即可。注意最后要把新树根的Father设为NULL,否则Splay时会判断根节点时会发生错误。
- cur有两个子节点。将GetPrevNode(cur)得到的结点Splay到树根,此时cur必然没有左孩子。这样把新树根与cur的右孩子相连即可。最后要把树的根设为这个新根。
GetRankByKey
得到key的排名。根据每个结点的各个子树大小,进行二叉树搜索即可。
GetKeyByRank
找第rank位的key值。用分治 。左孩子Size大于rank则左找,然后rank<=左孩子Size+点cur的Count刚好,得出结果, 否则向右孩子找,子问题中的rank等于当前的rank -(左孩子size+cur的count)。
GetPrevKey(易忽略点)
GetPrevKey(int key)是要找到严格比key小的Key值。注意题目不保证给出的key值已经加入到树中了。此处注意审题。
具体做法是:向图中加入一个key值结点,然后GetPrevNode那个结点得到结果,最后将新加入的结点删除。
GetNextKey与GetPrevKey相反。
- #include <cstdio>
- #include <cstring>
- #include <cassert>
- using namespace std;
- const int MAX_NODE = 100010, NoAnswer=0xcfcfcfcf;
- struct SplayTree
- {
- private:
- struct Node
- {
- Node *Father, *LeftSon, *RightSon;
- int Id, Key, Count, Size;
- Node(){}
- Node(int key):Key(key),Count(1),Father(NULL),LeftSon(NULL),RightSon(NULL){}
- bool IsLeftSon()
- {
- assert(Father);
- return Father->LeftSon == this;
- }
- bool IsRoot()
- {
- return Father == NULL || (Father->LeftSon != this &&Father->RightSon != this);
- }
- void Refresh()
- {
- Size = (LeftSon ? LeftSon->Size : 0) + (RightSon ? RightSon->Size : 0) + Count;
- }
- }_nodes[MAX_NODE], *Root;
- int _nodeCnt;
- void SetRoot(Node *cur)
- {
- Root = cur;
- if (Root)
- Root->Father = NULL;
- }
- Node *GetRoot()
- {
- return Root;
- }
- Node *NewNode(int key)
- {
- _nodeCnt++;
- _nodes[_nodeCnt] = Node(key);
- _nodes[_nodeCnt].Id = _nodeCnt;
- return &_nodes[_nodeCnt];
- }
- void Rotate(Node *cur)
- {
- Node *gfa = cur->Father->Father;
- Node **gfaSonP = cur->Father->IsRoot() ? &Root : (cur->Father->IsLeftSon() ? &gfa->LeftSon : &gfa->RightSon);//最好写cur->Father->IsRoot(),而非!Root
- Node **faSonP = cur->IsLeftSon() ? &cur->Father->LeftSon : &cur->Father->RightSon;
- Node **curSonP = cur->IsLeftSon() ? &cur->RightSon : &cur->LeftSon;
- *faSonP = *curSonP;
- if (*faSonP)
- (*faSonP)->Father = cur->Father;
- *curSonP = cur->Father;
- (*curSonP)->Father = cur;
- *gfaSonP = cur;
- (*gfaSonP)->Father = gfa;
- (*curSonP)->Refresh();
- cur->Refresh();
- }
- void PushDown(Node *cur) {}
- void Splay(Node *cur)
- {
- PushDown(cur);//易忘点
- while (!cur->IsRoot())
- {
- if (!cur->Father->IsRoot())
- Rotate(cur->IsLeftSon() == cur->Father->IsLeftSon() ? cur->Father : cur);
- Rotate(cur);
- }
- }
- Node *Find(int key)
- {
- Node *cur = GetRoot();
- if (cur == NULL)
- return NULL;//最好写上
- while (cur)
- {
- if (key == cur->Key)
- {
- Splay(cur);
- return cur;
- }
- cur = key < cur->Key ? cur->LeftSon : cur->RightSon;
- }
- return NULL;
- }
- Node *GetPrevNode(Node *cur)
- {
- if (!(cur = cur->LeftSon))
- return NULL;
- while (cur->RightSon)
- cur = cur->RightSon;
- return cur;
- }
- Node *GetNextNode(Node *cur)
- {
- if (!(cur = cur->RightSon))
- return NULL;
- while (cur->LeftSon)
- cur = cur->LeftSon;
- return cur;
- }
- int GetKeyByRank(Node *cur, int rank)
- {
- if (cur == NULL)
- return NoAnswer;//最好写上
- int leftSize = cur->LeftSon?cur->LeftSon->Size : 0, RootSize;//易忘点:判断
- if (leftSize >= rank)
- return GetKeyByRank(cur->LeftSon, rank);
- else if ((RootSize = leftSize + cur->Count) >= rank)
- return cur->Key;
- else
- return GetKeyByRank(cur->RightSon, rank - RootSize);
- }
- public:
- SplayTree()
- {
- memset(_nodes, 0, sizeof(_nodes));
- _nodeCnt = 0;
- }
- void Insert(int key)
- {
- Node **curP = &Root;
- Node *fa = NULL;
- while (*curP && (*curP)->Key != key)
- {
- fa = *curP;
- curP = key < (*curP)->Key ? &(*curP)->LeftSon : &(*curP)->RightSon;
- }
- if (*curP)
- (*curP)->Count++;
- else
- {
- *curP = NewNode(key);
- (*curP)->Father = fa;//此处不必cur->Father->Refresh()是因为下面Splay设置好了。
- }
- Splay(*curP);//易忘点
- }
- void Delete(int key)
- {
- Node *cur = Find(key);
- if (cur->Count > 1)
- cur->Count--;
- else if (!cur->LeftSon || !cur->RightSon)
- SetRoot(cur->LeftSon ? cur->LeftSon : cur->RightSon);
- else if (cur->LeftSon&&cur->RightSon)
- {
- Node *root = GetPrevNode(cur);
- Splay(root);
- root->RightSon = cur->RightSon;
- if (cur->RightSon)//易忘点
- cur->RightSon->Father = root;
- }
- }
- int GetRankByKey(int key)
- {
- Node *cur = Find(key);
- if (cur == NULL)
- return NoAnswer;
- return (cur->LeftSon ? cur->LeftSon->Size : 0) + 1;
- }
- int GetKeyByRank(int rank)
- {
- return GetKeyByRank(Root, rank);
- }
- int GetPrevKey(int key)
- {
- Insert(key);
- int ans = GetPrevNode(Find(key))->Key;
- Delete(key);
- return ans;
- }
- int GetNextKey(int key)
- {
- Insert(key);
- int ans = GetNextNode(Find(key))->Key;
- Delete(key);
- return ans;
- }
- }g;
- int main()
- {
- int opCnt, op, key, rank;
- scanf("%d", &opCnt);
- while (opCnt--)
- {
- scanf("%d", &op);
- switch (op)
- {
- case 1://Insert
- scanf("%d", &key);
- g.Insert(key);
- break;
- case 2://Delete
- scanf("%d", &key);
- g.Delete(key);
- break;
- case 3://GetRankByKey
- scanf("%d", &key);
- printf("%d\n", g.GetRankByKey(key));
- break;
- case 4://GetKeyByRank
- scanf("%d", &rank);
- printf("%d\n", g.GetKeyByRank(rank));
- break;
- case 5://GetPrevKey
- scanf("%d", &key);
- printf("%d\n", g.GetPrevKey(key));
- break;
- case 6://GetNextKey
- scanf("%d", &key);
- printf("%d\n", g.GetNextKey(key));
- break;
- }
- }
- return 0;
- }
luogu3369 【模板】 普通平衡树 Splay的更多相关文章
- luoguP3391[模板]文艺平衡树(Splay) 题解
链接一下题目:luoguP3391[模板]文艺平衡树(Splay) 平衡树解析 这里的Splay维护的显然不再是权值排序 现在按照的是序列中的编号排序(不过在这道题目里面就是权值诶...) 那么,继续 ...
- [luogu3369/bzoj3224]普通平衡树(splay模板、平衡树初探)
解题关键:splay模板题整理. 如何不加入极大极小值?(待思考) #include<cstdio> #include<cstring> #include<algorit ...
- 洛谷.3369.[模板]普通平衡树(Splay)
题目链接 第一次写(2017.11.7): #include<cstdio> #include<cctype> using namespace std; const int N ...
- 洛谷.3391.[模板]文艺平衡树(Splay)
题目链接 //注意建树 #include<cstdio> #include<algorithm> const int N=1e5+5; //using std::swap; i ...
- 【阶梯报告】洛谷P3391【模板】文艺平衡树 splay
[阶梯报告]洛谷P3391[模板]文艺平衡树 splay 题目链接在这里[链接](https://www.luogu.org/problemnew/show/P3391)最近在学习splay,终于做对 ...
- 【模板篇】splay(填坑)+模板题(普通平衡树)
划着划着水一不小心NOIP还考的凑合了… 所以退役的打算要稍微搁置一下了… 要准备准备省选了…. 但是自己已经啥也不会了… 所以只能重新拾起来… 从splay开始吧… splay我以前扔了个板子来着, ...
- 【BZOJ3224】Tyvj 1728 普通平衡树 Splay
Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数 ...
- BZOJ3224/洛谷P3391 - 普通平衡树(Splay)
BZOJ链接 洛谷链接 题意简述 模板题啦~ 代码 //普通平衡树(Splay) #include <cstdio> int const N=1e5+10; int rt,ndCnt; i ...
- luoguP3369[模板]普通平衡树(Treap/SBT) 题解
链接一下题目:luoguP3369[模板]普通平衡树(Treap/SBT) 平衡树解析 #include<iostream> #include<cstdlib> #includ ...
- 平衡树——splay 一
splay 一种平衡树,同时也是二叉排序树,与treap不同,它不需要维护堆的性质,它由Daniel Sleator和Robert Tarjan(没错,tarjan,又是他)创造,伸展树是一种自调整二 ...
随机推荐
- 菜鸟使用 centOS 安装 redis 并放入service 启动 记录
1.下载redis: wget http://download.redis.io/releases/redis-2.8.17.tar.gz 若wget 不可用,请先安装wget yum install ...
- 怎么不让别人ping服务器
频繁地使用Ping命令会导致网络堵塞.降低传输效率,为了避免恶意的网络攻击,一般都会拒绝用户Ping服务器.为实现这一目的,不仅可以在防火墙中进 行设置,也可以在路由器上进行设置,并且还可以利用Win ...
- Redmine使用指南
公司之前使用JIRA登bug,但是客户在美国,他们习惯于用Redmine登bug,所以我们也开始在Redmine登bug,找来一个比较全面的Redmine使用指南,不懂时直接查看. http://bl ...
- Windows 10 新功能
一.与 Cortana 集成的便笺 借助便笺,你可捕捉并保存绝妙创意或记录重要细节.便笺现已与 Cortana 集成,让你能够设置整个设备中的提醒. (一) 先来了解一下微软小娜Cortana. ...
- 【汇编】MASM6.15几个简单的汇编程序
/***************通过调用(INT 21H)表中的01h号功能号从键盘输入一个字符并回显到视频显示器上*****************/ DATAS SEGMENT ;此处输入数据段代 ...
- java 循环document 通用替换某个字符串或特殊字符
document 生成xml时 报错 XML-20100: (Fatal Error) Expected ';'. 查了半天发现是 特殊字符 & 不能直接转出,需要进行转换,因为是通用方法很 ...
- python tips:小整数对象池与字符串intern
本文为is同一性运算符的详细解释.is用于判断两个对象是否为同一个对象,具体来说是两个对象在内存中的位置是否相同. python为了提高效率,节省内存,在实现上大量使用了缓冲池技术和字符串intern ...
- Django的Error汇总
title: Django学习笔记 catalog: true subtitle: 11. Django_Error汇总 date: 2018-12-14 10:17:28 --- Django的Er ...
- 单调队列 && 单调栈
单调队列 && 单调栈 单调队列 维护某个滑动区间的min or max,可用于dp的优化 以维护min为例,采用STL双端队列实现 每次加入元素x前 先检查队首元素==滑动后要删除的 ...
- Doxyfile中插入图片
下面讲一下如何在doxyfile中插入图片 在查看别人写的文档的过程中,看到可以在doxyfile中插入图片,对此十分的好奇,所以拿出来研究一下 那么这是如何实现的? 根据代码,可以看到如下的注释 @ ...