题目大意

维护一个数据结构,满足以下操作:

  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数)
  6. 求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相反。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <cassert>
  4. using namespace std;
  5.  
  6. const int MAX_NODE = 100010, NoAnswer=0xcfcfcfcf;
  7.  
  8. struct SplayTree
  9. {
  10. private:
  11. struct Node
  12. {
  13. Node *Father, *LeftSon, *RightSon;
  14. int Id, Key, Count, Size;
  15.  
  16. Node(){}
  17. Node(int key):Key(key),Count(1),Father(NULL),LeftSon(NULL),RightSon(NULL){}
  18.  
  19. bool IsLeftSon()
  20. {
  21. assert(Father);
  22. return Father->LeftSon == this;
  23. }
  24.  
  25. bool IsRoot()
  26. {
  27. return Father == NULL || (Father->LeftSon != this &&Father->RightSon != this);
  28. }
  29.  
  30. void Refresh()
  31. {
  32. Size = (LeftSon ? LeftSon->Size : 0) + (RightSon ? RightSon->Size : 0) + Count;
  33. }
  34. }_nodes[MAX_NODE], *Root;
  35. int _nodeCnt;
  36.  
  37. void SetRoot(Node *cur)
  38. {
  39. Root = cur;
  40. if (Root)
  41. Root->Father = NULL;
  42. }
  43.  
  44. Node *GetRoot()
  45. {
  46. return Root;
  47. }
  48.  
  49. Node *NewNode(int key)
  50. {
  51. _nodeCnt++;
  52. _nodes[_nodeCnt] = Node(key);
  53. _nodes[_nodeCnt].Id = _nodeCnt;
  54. return &_nodes[_nodeCnt];
  55. }
  56.  
  57. void Rotate(Node *cur)
  58. {
  59. Node *gfa = cur->Father->Father;
  60. Node **gfaSonP = cur->Father->IsRoot() ? &Root : (cur->Father->IsLeftSon() ? &gfa->LeftSon : &gfa->RightSon);//最好写cur->Father->IsRoot(),而非!Root
  61. Node **faSonP = cur->IsLeftSon() ? &cur->Father->LeftSon : &cur->Father->RightSon;
  62. Node **curSonP = cur->IsLeftSon() ? &cur->RightSon : &cur->LeftSon;
  63. *faSonP = *curSonP;
  64. if (*faSonP)
  65. (*faSonP)->Father = cur->Father;
  66. *curSonP = cur->Father;
  67. (*curSonP)->Father = cur;
  68. *gfaSonP = cur;
  69. (*gfaSonP)->Father = gfa;
  70. (*curSonP)->Refresh();
  71. cur->Refresh();
  72. }
  73.  
  74. void PushDown(Node *cur) {}
  75.  
  76. void Splay(Node *cur)
  77. {
  78. PushDown(cur);//易忘点
  79. while (!cur->IsRoot())
  80. {
  81. if (!cur->Father->IsRoot())
  82. Rotate(cur->IsLeftSon() == cur->Father->IsLeftSon() ? cur->Father : cur);
  83. Rotate(cur);
  84. }
  85. }
  86.  
  87. Node *Find(int key)
  88. {
  89. Node *cur = GetRoot();
  90. if (cur == NULL)
  91. return NULL;//最好写上
  92. while (cur)
  93. {
  94. if (key == cur->Key)
  95. {
  96. Splay(cur);
  97. return cur;
  98. }
  99. cur = key < cur->Key ? cur->LeftSon : cur->RightSon;
  100. }
  101. return NULL;
  102. }
  103.  
  104. Node *GetPrevNode(Node *cur)
  105. {
  106. if (!(cur = cur->LeftSon))
  107. return NULL;
  108. while (cur->RightSon)
  109. cur = cur->RightSon;
  110. return cur;
  111. }
  112.  
  113. Node *GetNextNode(Node *cur)
  114. {
  115. if (!(cur = cur->RightSon))
  116. return NULL;
  117. while (cur->LeftSon)
  118. cur = cur->LeftSon;
  119. return cur;
  120. }
  121.  
  122. int GetKeyByRank(Node *cur, int rank)
  123. {
  124. if (cur == NULL)
  125. return NoAnswer;//最好写上
  126. int leftSize = cur->LeftSon?cur->LeftSon->Size : 0, RootSize;//易忘点:判断
  127. if (leftSize >= rank)
  128. return GetKeyByRank(cur->LeftSon, rank);
  129. else if ((RootSize = leftSize + cur->Count) >= rank)
  130. return cur->Key;
  131. else
  132. return GetKeyByRank(cur->RightSon, rank - RootSize);
  133. }
  134.  
  135. public:
  136. SplayTree()
  137. {
  138. memset(_nodes, 0, sizeof(_nodes));
  139. _nodeCnt = 0;
  140. }
  141.  
  142. void Insert(int key)
  143. {
  144. Node **curP = &Root;
  145. Node *fa = NULL;
  146. while (*curP && (*curP)->Key != key)
  147. {
  148. fa = *curP;
  149. curP = key < (*curP)->Key ? &(*curP)->LeftSon : &(*curP)->RightSon;
  150. }
  151. if (*curP)
  152. (*curP)->Count++;
  153. else
  154. {
  155. *curP = NewNode(key);
  156. (*curP)->Father = fa;//此处不必cur->Father->Refresh()是因为下面Splay设置好了。
  157. }
  158. Splay(*curP);//易忘点
  159. }
  160.  
  161. void Delete(int key)
  162. {
  163. Node *cur = Find(key);
  164. if (cur->Count > 1)
  165. cur->Count--;
  166. else if (!cur->LeftSon || !cur->RightSon)
  167. SetRoot(cur->LeftSon ? cur->LeftSon : cur->RightSon);
  168. else if (cur->LeftSon&&cur->RightSon)
  169. {
  170. Node *root = GetPrevNode(cur);
  171. Splay(root);
  172. root->RightSon = cur->RightSon;
  173. if (cur->RightSon)//易忘点
  174. cur->RightSon->Father = root;
  175. }
  176. }
  177.  
  178. int GetRankByKey(int key)
  179. {
  180. Node *cur = Find(key);
  181. if (cur == NULL)
  182. return NoAnswer;
  183. return (cur->LeftSon ? cur->LeftSon->Size : 0) + 1;
  184. }
  185.  
  186. int GetKeyByRank(int rank)
  187. {
  188. return GetKeyByRank(Root, rank);
  189. }
  190.  
  191. int GetPrevKey(int key)
  192. {
  193. Insert(key);
  194. int ans = GetPrevNode(Find(key))->Key;
  195. Delete(key);
  196. return ans;
  197. }
  198.  
  199. int GetNextKey(int key)
  200. {
  201. Insert(key);
  202. int ans = GetNextNode(Find(key))->Key;
  203. Delete(key);
  204. return ans;
  205. }
  206. }g;
  207.  
  208. int main()
  209. {
  210. int opCnt, op, key, rank;
  211. scanf("%d", &opCnt);
  212. while (opCnt--)
  213. {
  214. scanf("%d", &op);
  215. switch (op)
  216. {
  217. case 1://Insert
  218. scanf("%d", &key);
  219. g.Insert(key);
  220. break;
  221. case 2://Delete
  222. scanf("%d", &key);
  223. g.Delete(key);
  224. break;
  225. case 3://GetRankByKey
  226. scanf("%d", &key);
  227. printf("%d\n", g.GetRankByKey(key));
  228. break;
  229. case 4://GetKeyByRank
  230. scanf("%d", &rank);
  231. printf("%d\n", g.GetKeyByRank(rank));
  232. break;
  233. case 5://GetPrevKey
  234. scanf("%d", &key);
  235. printf("%d\n", g.GetPrevKey(key));
  236. break;
  237. case 6://GetNextKey
  238. scanf("%d", &key);
  239. printf("%d\n", g.GetNextKey(key));
  240. break;
  241. }
  242. }
  243. return 0;
  244. }

  

luogu3369 【模板】 普通平衡树 Splay的更多相关文章

  1. luoguP3391[模板]文艺平衡树(Splay) 题解

    链接一下题目:luoguP3391[模板]文艺平衡树(Splay) 平衡树解析 这里的Splay维护的显然不再是权值排序 现在按照的是序列中的编号排序(不过在这道题目里面就是权值诶...) 那么,继续 ...

  2. [luogu3369/bzoj3224]普通平衡树(splay模板、平衡树初探)

    解题关键:splay模板题整理. 如何不加入极大极小值?(待思考) #include<cstdio> #include<cstring> #include<algorit ...

  3. 洛谷.3369.[模板]普通平衡树(Splay)

    题目链接 第一次写(2017.11.7): #include<cstdio> #include<cctype> using namespace std; const int N ...

  4. 洛谷.3391.[模板]文艺平衡树(Splay)

    题目链接 //注意建树 #include<cstdio> #include<algorithm> const int N=1e5+5; //using std::swap; i ...

  5. 【阶梯报告】洛谷P3391【模板】文艺平衡树 splay

    [阶梯报告]洛谷P3391[模板]文艺平衡树 splay 题目链接在这里[链接](https://www.luogu.org/problemnew/show/P3391)最近在学习splay,终于做对 ...

  6. 【模板篇】splay(填坑)+模板题(普通平衡树)

    划着划着水一不小心NOIP还考的凑合了… 所以退役的打算要稍微搁置一下了… 要准备准备省选了…. 但是自己已经啥也不会了… 所以只能重新拾起来… 从splay开始吧… splay我以前扔了个板子来着, ...

  7. 【BZOJ3224】Tyvj 1728 普通平衡树 Splay

    Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数 ...

  8. BZOJ3224/洛谷P3391 - 普通平衡树(Splay)

    BZOJ链接 洛谷链接 题意简述 模板题啦~ 代码 //普通平衡树(Splay) #include <cstdio> int const N=1e5+10; int rt,ndCnt; i ...

  9. luoguP3369[模板]普通平衡树(Treap/SBT) 题解

    链接一下题目:luoguP3369[模板]普通平衡树(Treap/SBT) 平衡树解析 #include<iostream> #include<cstdlib> #includ ...

  10. 平衡树——splay 一

    splay 一种平衡树,同时也是二叉排序树,与treap不同,它不需要维护堆的性质,它由Daniel Sleator和Robert Tarjan(没错,tarjan,又是他)创造,伸展树是一种自调整二 ...

随机推荐

  1. 菜鸟使用 centOS 安装 redis 并放入service 启动 记录

    1.下载redis: wget http://download.redis.io/releases/redis-2.8.17.tar.gz 若wget 不可用,请先安装wget yum install ...

  2. 怎么不让别人ping服务器

    频繁地使用Ping命令会导致网络堵塞.降低传输效率,为了避免恶意的网络攻击,一般都会拒绝用户Ping服务器.为实现这一目的,不仅可以在防火墙中进 行设置,也可以在路由器上进行设置,并且还可以利用Win ...

  3. Redmine使用指南

    公司之前使用JIRA登bug,但是客户在美国,他们习惯于用Redmine登bug,所以我们也开始在Redmine登bug,找来一个比较全面的Redmine使用指南,不懂时直接查看. http://bl ...

  4. Windows 10 新功能

    一.与 Cortana 集成的便笺 借助便笺,你可捕捉并保存绝妙创意或记录重要细节.便笺现已与 Cortana 集成,让你能够设置整个设备中的提醒. (一)   先来了解一下微软小娜Cortana. ...

  5. 【汇编】MASM6.15几个简单的汇编程序

    /***************通过调用(INT 21H)表中的01h号功能号从键盘输入一个字符并回显到视频显示器上*****************/ DATAS SEGMENT ;此处输入数据段代 ...

  6. java 循环document 通用替换某个字符串或特殊字符

    document 生成xml时 报错 XML-20100: (Fatal Error) Expected ';'.  查了半天发现是 特殊字符 & 不能直接转出,需要进行转换,因为是通用方法很 ...

  7. python tips:小整数对象池与字符串intern

    本文为is同一性运算符的详细解释.is用于判断两个对象是否为同一个对象,具体来说是两个对象在内存中的位置是否相同. python为了提高效率,节省内存,在实现上大量使用了缓冲池技术和字符串intern ...

  8. Django的Error汇总

    title: Django学习笔记 catalog: true subtitle: 11. Django_Error汇总 date: 2018-12-14 10:17:28 --- Django的Er ...

  9. 单调队列 && 单调栈

    单调队列 && 单调栈 单调队列 维护某个滑动区间的min or max,可用于dp的优化 以维护min为例,采用STL双端队列实现 每次加入元素x前 先检查队首元素==滑动后要删除的 ...

  10. Doxyfile中插入图片

    下面讲一下如何在doxyfile中插入图片 在查看别人写的文档的过程中,看到可以在doxyfile中插入图片,对此十分的好奇,所以拿出来研究一下 那么这是如何实现的? 根据代码,可以看到如下的注释 @ ...