初识splay

学splay有一段时间了,一直没写......

本题是splay模板题,维护一个1~n的序列,支持区间翻转(比如1 2 3 4 5 6变成1 2 3 6 5 4),最后输出结果序列。

模板题嘛......主要了解一下splay的基本操作QwQ

原题地址链接[洛谷P3391文艺平衡树]

1.基本概念

splay是一种二叉搜索树,节点的权值满足lson<p<rson,故可以像其他二叉搜索树一样在树上二分查找某数排名,排名为k的数,以及前驱后继等。

普通的二叉搜索树在面对特殊数据时树的深度会从log n退化成接近n(退化成链),这样操作的时间复杂度会从O(log n)退化成O(n),影响效率。

splay通过旋转维持树的平衡。这个操作后面会提到。

2.基本操作

二叉搜索树的基本操作:求排名为k的数。

 1 int rank(int p,int k)
 2 {
 3     pushdown(p);
 4     if(k<=sz[s[p][0]])
 5         return rank(s[p][0],k);
 6     else if(k==sz[s[p][0]]+1)
 7         return p;
 8     else
 9         return rank(s[p][1],k-sz[s[p][0]]-1);
10 }

简单的树上二分。

3.核心操作:splay

splay的精髓在于骚气的旋转。(名字就是这么来的哈哈哈~)

splay的核心操作是splay(一脸懵逼),splay(x,y)意为通过一系列旋转,将点x旋转到点y下面,使x成为y的儿子。

每次旋转通过rotate函数实现:

 1 void rotate(int p)
 2 {
 3     int fa=f[p];
 4     bool k=id(p);
 5     s[fa][k]=s[p][!k];
 6     s[p][!k]=fa;
 7     s[f[fa]][id(fa)]=p;
 8     f[p]=f[fa];
 9     f[s[fa][k]]=fa;
10     f[fa]=p;
11     refresh(fa);
12     refresh(p);
13 }

rotate的时候严格满足splay二叉搜索树的性质:lson<p<rson。

将p提到fa的位置,根据大小关系决定fa是作为p的左儿子还是右儿子,这样实际上是fa挤掉了p原先的某个儿子,而p转上去,让出了fa的一个儿子的位置。

所以最后让那个被fa挤掉的p的孤儿作为fa的某个儿子,填到空缺的地方去(原来p的位置)。

至于splay的实现方法...有两种:单旋和双旋。

单旋即无脑地一直转,直到把x转到y下面。

1 void splay(int p,int g)  // 单旋
2 {
3     while(f[p]!=g)rotate(p);
4     if(!g)root=p;
5 }

比起单旋,双旋能更好的维护splay的平衡。

 1 void splay(int p,int g) // 双旋
 2 {
 3     while(f[p]!=g)
 4     {
 5         int fa=f[p];
 6         if(f[fa]==g)
 7         {
 8             rotate(p);
 9             break;
10         }
11         if(id(p)^id(fa))rotate(p);
12         else rotate(fa);
13         rotate(p);
14     }
15     if(!g)root=p;
16 }

利用splay操作,我们就可以用这棵树实现很多其它平衡树实现不了的功能。

4.元素的插入、删除、查询及修改

设x为 要插入的/要删除的/要查询的/要修改的 元素or区间。

进行这些操作之前,运用旋转操作把x的前驱pre转到根位置,把x的后继post转到根的下面,post>pre,所以此时post一定是pre的右儿子。

(如果是区间,pre就是left的前驱,post就是right的后继)

如图:

此时,根据二叉搜索树的性质,要删除/查询/修改的元素or区间就一定在post的左子树那里。如图:(目标子树:红色部分)

4.1 插入

如果是插入,红色部分一定为空,在那里插入即可。

4.2 删除

残忍抛弃红色部分。

4.3 查询

在红色部分查询。

4.4 修改

在这道题里是区间翻转。

我们并不需要真的翻转,打个标记就行。

标记需要下传的时候,交换左右子树的左右子树,在左右儿子上打标记,清掉自身标记。

1 void pushdown(int p)
2 {
3     if(!fl[p])return;
4     fl[s[p][0]]^=1;
5     fl[s[p][1]]^=1;
6     swap(s[s[p][0]][0],s[s[p][0]][1]);
7     swap(s[s[p][1]][0],s[s[p][1]][1]);
8     fl[p]=0;
9 }

这样就行了。

完事了?

完事了。

最后二分输出序列即可。

其他细节见代码。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #define N 100005
  5 #define id(x) (s[f[x]][1]==x) // 判断是左儿子还是右儿子
  6 using namespace std;
  7
  8 int f[N],s[N][2],val[N],sz[N],root,tot; // 分别是父亲,儿子,值,子树大小,树根,元素数量
  9 bool fl[N]; // 翻转标记
 10
 11 void refresh(int p) // 更新size
 12 {
 13     sz[p]=sz[s[p][0]]+sz[s[p][1]]+1;
 14 }
 15
 16 void pushdown(int p) // 下传标记
 17 {
 18     if(!fl[p])return;
 19     fl[s[p][0]]^=1;
 20     fl[s[p][1]]^=1;
 21     swap(s[s[p][0]][0],s[s[p][0]][1]);
 22     swap(s[s[p][1]][0],s[s[p][1]][1]);
 23     fl[p]=0;
 24 }
 25
 26 void rotate(int p) // 把p转上去
 27 {
 28     int fa=f[p];
 29     bool k=id(p);
 30     s[fa][k]=s[p][!k];
 31     s[p][!k]=fa;
 32     s[f[fa]][id(fa)]=p;
 33     f[p]=f[fa];
 34     f[s[fa][k]]=fa;
 35     f[fa]=p;
 36     refresh(fa);
 37     refresh(p);
 38 }
 39 /*
 40 void splay(int p,int g) // 单旋
 41 {
 42     while(f[p]!=g)rotate(p);
 43     if(!g)root=p;
 44 }
 45 */
 46 void splay(int p,int g) // 双旋
 47 {
 48     while(f[p]!=g)
 49     {
 50         int fa=f[p];
 51         if(f[fa]==g)
 52         {
 53             rotate(p);
 54             break;
 55         }
 56         if(id(p)^id(fa))rotate(p);
 57         else rotate(fa);
 58         rotate(p);
 59     }
 60     if(!g)root=p;
 61 }
 62
 63 int rank(int p,int k) // 查询rank为k的元素
 64 {
 65     pushdown(p);
 66     if(k<=sz[s[p][0]])
 67         return rank(s[p][0],k);
 68     else if(k==sz[s[p][0]]+1)
 69         return p;
 70     else
 71         return rank(s[p][1],k-sz[s[p][0]]-1);
 72 }
 73
 74 int build(int l,int r,int fa) // 建树  实际上一个一个插入也行,但是这样二分建树可以使初始树更平衡
 75 {
 76     if(l>r)return 0;
 77     int mid=(l+r)>>1;
 78     int p=++tot;
 79     s[p][0]=build(l,mid-1,p);
 80     s[p][1]=build(mid+1,r,p);
 81     val[p]=mid;
 82     f[p]=fa;
 83     refresh(p);
 84     return p;
 85 }
 86
 87 void change(int l,int r) // 区间翻转
 88 {
 89     int pre,post,rt;
 90     pre=rank(root,l-1);
 91     splay(pre,0);
 92     post=rank(root,r+1);
 93     splay(post,pre);
 94     rt=s[post][0];
 95     swap(s[rt][0],s[rt][1]);
 96     fl[rt]^=1;
 97 }
 98
 99 void print(int p) // 二分输出结果序列
100 {
101     if(!p)return;
102     pushdown(p);
103     print(s[p][0]);
104     printf("%d ",val[p]);
105     print(s[p][1]);
106 }
107
108 int n,m;
109
110 int main()
111 {
112     scanf("%d%d",&n,&m);
113     root=build(0,n+1,0);
114     for(int i=1;i<=m;i++)
115     {
116         int lb,rb;
117         scanf("%d%d",&lb,&rb);
118         change(lb+1,rb+1);
119     }
120     splay(rank(root,1),0);
121     splay(rank(root,n+2),root);
122     print(s[s[root][1]][0]);
123     return 0;
124 }
125
126 complete code of splay tree

complete code of splay tree

转载于:https://www.cnblogs.com/eternhope/p/9474709.html

[洛谷P3391] 文艺平衡树 (Splay模板)相关推荐

  1. 浅尝无旋Treap (基于洛谷P3391 文艺平衡树)

    说是浅尝吧,确实也挺浅的,完全是基于下面这道题写的↓ 洛谷P3391 自己去看题,我是懒得粘了... 分析 其实也没有什么好分析的,这就是一道Splay树的模板题,解决一般的Treap不能解决的区间维 ...

  2. 洛谷P3391文艺平衡树(Splay)

    题目传送门 转载自https://www.cnblogs.com/yousiki/p/6147455.html,转载请注明出处 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 ...

  3. 洛谷 P3391 文艺平衡树

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 --b ...

  4. [洛谷P3391]文艺平衡树

    题目 传送门 to luogu 思路 题外话:一开始用Splay\text{Splay}Splay写了一发,结果-- 然后就换成了Treap\text{Treap}Treap.嗯,无旋Treap\te ...

  5. 洛谷 P3391 【模板】文艺平衡树

    题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构,来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4 ...

  6. BZOJ 3223: Tyvj 1729 文艺平衡树-Splay树(区间翻转)模板题

    3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 6881  Solved: 4213 [Submit][S ...

  7. 洛谷 P3380 bzoj3196 Tyvj1730 【模板】二逼平衡树(树套树)

    [模板]二逼平衡树(树套树) 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作: 查询k在区间内的排名 查询区间内排名为k的值 修改某一位值上的数值 查询k在 ...

  8. BZOJ 3223: Tyvj 1729 文艺平衡树(splay)

    速度居然进前十了...第八... splay, 区间翻转,用一个类似线段树的lazy标记表示是否翻转 ------------------------------------------------- ...

  9. 洛谷 - P3391 【模板】文艺平衡树(Splay-区间反转)

    题目链接:点击查看 题目大意:给出一个初始时 a[ i ] = i 的序列,给出 m 次操作,每次操作将区间 [ l , r ] 内的数列反转,问最终的数列是什么 题目分析:Splay处理区间反转问题 ...

最新文章

  1. 10 Windows编程——鼠标消息
  2. .Net winform中嵌入Flash
  3. struts.xml 文件添加DTD文件
  4. Java Spring Boot 2.0 实战之制作Docker镜像并推送到Docker Hub和阿里云仓库
  5. java stringbuffer详解_Java常用类StringBuffer详解
  6. codeforces1484 B. Restore Modulo(数学)
  7. 牛客16785 Cantor表
  8. Stack应用 - 数学表达式(Balanced parentheses)括号完全匹配
  9. Java 数据结构之双链表
  10. 智慧校园大数据平台建设和运营整体解决方案
  11. OpenLayers 加载 百度 地图
  12. android全面屏像素密度,屏幕像素密度超400ppi,让你感受视觉的极限
  13. 现在哪个 UI 会写代码?
  14. 如何调整gif动图的速度?1分钟在线调节gif动图速度
  15. Java实现 已知ListString list = new ArrayListString();list .add(张三丰,北京);......要求:求出每个地区有多少人,都是谁?
  16. HW--DSF服务配置文件
  17. android 地图定位失败,Android 高德地图定位遇到的异常
  18. php远程下载到本地,PHP 下载远程文件到本地的简单示例
  19. 用 shader effect 实现雨滴落水效果!Cocos Creator 3D !
  20. 激活函数的作用是什么?

热门文章

  1. CentOS 6.5 安装 Python3
  2. 关于Servlet报错:405 HTTP method GET is not supported by this URL问题解决方法
  3. 下一个互联网平台还有多远?
  4. 对于已经加入版本控制的文件,我们可以强制忽略文件git update-index --assume-unchanged local.properties...
  5. Portal: 西电捷通TISec®技术
  6. php拼音模糊搜索,前端拼音模糊搜索
  7. es和oracle,Oracle和Elasticsearch数据同步
  8. 平滑线反锯齿工具_Photoshop中的华丽渐变工具的使用
  9. 【java学习之路】(javaWeb【后端】篇)002.Servlet
  10. ibm服务器系统电池型号,IBM服务器_X366型号2003系统恢复