【阶梯报告】洛谷P3391【模板】文艺平衡树 splay
【阶梯报告】洛谷P3391【模板】文艺平衡树 splay
题目链接在这里[链接](https://www.luogu.org/problemnew/show/P3391)
最近在学习splay,终于做对了这道模版题,虽然不是很难的样子。~~但是我一开始并不会做,而且看完题解之后还打错一直打不对,调试了很久~~
下面是题目简述
现在给你一个长度为n的序列,序列元素初始为1,2,3...n,同时有m个操作,每个操作给定一个L和R,表示将[L,R]区间的数进行翻转。
输出:完成所有操作之后的序列(n,m≤100000)
首先,这道题用暴力是肯定超时的。但是既然连题目都提示用splay做这道题了,那么我们自然可以用splay做这道题。
首先我们想一下splay的树的性质。
### 性质
1. 如果我们给每一个点添加一个键值,这个键值表示的是这个点在序列中的位置,那么我们对splay树进行中序遍历之后,得到的序列就是原来对应的序列。
2. 对于splay树,无论是进行zip还是zap操作,都不会影响最终中序遍历的结果。因为zip和zap都是在保证二叉查找树的性质的前提下进行的。
3. 假如我们splay(L-1,root),然后,splay(R+1,root->right_child),那么我们就会惊奇地发现,区间[L,R]内的所有点都在R+1这个结点的左子树种。
4. 假如我们把任意一个结点的子树中的每一个结点的左右子树都换边,(包括自己)那么最终中序遍历的结果就是——除了被翻转的子树以外,其他所有点的中序遍历都没有变化,而子树中的所有节点(包括子树根节点)的中序遍历刚刚好反了过来。(你们可以自己画一个图试一下,或者说,如下图)
思路
第一种简陋的想法
1. 以1,2,3,4...n为键值插入splay树(或者直接构建一棵平衡树就行,因为你已经知道会有哪些元素插入树中)。之后,我们对树的所有操作都不需要用到键值,直接根据zip和zap不会改变中序遍历的特性进行改变。
2. 翻转区间的时候,先splay(l-1,root),splay(r+1,root->right_child),然后再把r+1这个结点的左子树的所有左右孩子交换即可。
虽然这么做确实可以做对题目,但是这样会有几个需要注意的地方。
1 . 假如要reserve(1,n),那么我们将会splay(0,root)和splay(n+1,root->left_child),但是树中没有0和n+1这两个结点。因此一开始的时候我们就应该加入INF和-INF,充当卫兵的作用。
2 . 交换子树中所有结点的速度实在是太慢了,和o(n)压根没有区别。这样根本就没有变快。
优化的方法
1. 翻转树的时候不需要把整个子树都交换,只要采取线段树的懒标记的方法做一个标记即可。
- 标记完之后,假如等一下再标记同样的区间,只要异或一下1就可以了
- 假如要splay的点在已经翻转过的子树里面,那么我们到时候只要在寻找结点的过程中进行push_down就可以了。
- 在输出整棵splay树的时候,也注意一下push_down就可以了。
具体方法
1. 把所有的元素都塞进splay里面。由于一开始就是有序的,所以你既可以一个一个地insert进去,也可以直接就构建一个十分平衡的splay树
2. 对于每一个翻转操作,首先splay(l-1,root),splay(r+1,root->right_child),然后再把r+1的左子树打上标记即可
3. 要点:每次在find一个结点的时候,一定要先push_down,然后再进行操作。
4. 最后输出整棵树的时候也注意一下push_down
下面是我的代码,可能不是很看得明白。
#include<cstdio>
using namespace std;
const int N=;
const int INF=;
struct node
{
int lc,rc,fa,size,val,mark;//mark就是懒标记,val是本来的值,size是子树(包括自己)的大小
node()
{
lc=rc=fa=size=val=mark=;
}
}tree[N];
int root=,tot=,FIRST=;
void build(int,int,int,int);//直接用递归在刚开始的时候建立一棵比较平衡的树
void push_down(int);
void zip(int);//左旋
void zap(int);//右旋
void initailize();//放入INT和-INF作为卫兵
void splay(int,int);
int find(int);//用于寻找splay树中的第几大的数。毕竟在序列中排第k的数就是在splay中第k大的数
void reverse(int,int);
void print(int);//用递归输出整棵树
int main()
{
int n,m;
scanf("%d%d",&n,&m);
build(,,n,);
initailize();
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
reverse(l+,r+);//由于有-INF的存在,所以翻转[l,r]区间的时候,其实是翻转[l+1,r+1]区间
}
print(root);
return ;
}
void build(int x,int l,int r,int father)
{
tree[x].fa=father;
tree[x].size=;
if(l==r)
{
tree[x].val=l;
return;
}
int mid=(l+r)/;
tree[x].val=mid;
if(mid==l)
{
tree[x].rc=++tot;
build(tot,r,r,x);
tree[x].size+=tree[tree[x].rc].size;
}
else
{
tree[x].lc=++tot;
build(tot,l,mid-,x);
tree[x].rc=++tot;
build(tot,mid+,r,x);
tree[x].size+=tree[tree[x].lc].size+tree[tree[x].rc].size;
}
}
void initailize()
{
int p=root;
while(tree[p].lc!=) tree[p].size++,p=tree[p].lc;
tree[p].size++;
tree[p].lc=++tot;
tree[tot].fa=p;
tree[tot].size=;
tree[tot].val=-INF;
p=root;
while(tree[p].rc!=) tree[p].size++,p=tree[p].rc;
tree[p].size++;
tree[p].rc=++tot;
tree[tot].fa=p;
tree[tot].size=;
tree[tot].val=INF;
}
void inline push_down(int x)
{
if(tree[x].mark)
{
int L=tree[x].lc,R=tree[x].rc;
tree[L].mark^=;
tree[R].mark^=;
tree[x].mark=;
tree[x].lc=R;
tree[x].rc=L;
}
}
void zip(int x)
{
int y=tree[x].fa;
tree[y].rc=tree[x].lc;
tree[x].lc=y;
tree[x].fa=tree[y].fa;
tree[y].fa=x;
if(tree[x].fa)
if(tree[tree[x].fa].lc==y)
tree[tree[x].fa].lc=x;
else tree[tree[x].fa].rc=x;
else root=x;
if(tree[y].rc)
tree[tree[y].rc].fa=y;
tree[y].size=tree[tree[y].lc].size+tree[tree[y].rc].size+;
tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+;
}
void zap(int x)
{
int y=tree[x].fa;
tree[y].lc=tree[x].rc;
tree[x].rc=y;
tree[x].fa=tree[y].fa;
tree[y].fa=x;
if(tree[x].fa)
if(tree[tree[x].fa].lc==y)
tree[tree[x].fa].lc=x;
else tree[tree[x].fa].rc=x;
else root=x;
if(tree[y].lc)
tree[tree[y].lc].fa=y;
tree[y].size=tree[tree[y].lc].size+tree[tree[y].rc].size+;
tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+;
}
void splay(int x,int aim)
{
aim=tree[aim].fa;
while(tree[x].fa!=aim)
{
int y=tree[x].fa;
int z=tree[y].fa;
if(z==aim)
if(tree[y].lc==x)
zap(x);
else zip(x);
else if(tree[z].lc==y&&tree[y].lc==x)
zap(x),zap(x);
else if(tree[z].rc==y&&tree[y].rc==x)
zip(x),zip(x);
else if(tree[z].lc==y)
zip(x),zap(x);
else zap(x),zip(x);
}
}
int find(int k)
{
int p=root;
while()
{
push_down(p);
if(tree[tree[p].lc].size>=k)
p=tree[p].lc;
else
{
k-=tree[tree[p].lc].size;
if(k==) return p;
k-=;
p=tree[p].rc;
}
}
}
void reverse(int l,int r)
{
int L=find(l-);
splay(L,root);
int R=find(r+);
splay(R,tree[L].rc);
tree[tree[R].lc].mark^=;
}
void print(int x)
{
if(x==) return;
push_down(x);
print(tree[x].lc);
if(tree[x].val!=INF&&tree[x].val!=-INF)
{
if(FIRST)
{
FIRST=;
printf("%d",tree[x].val);
}
else printf(" %d",tree[x].val);
}
print(tree[x].rc);
}
对了,顺便说一下,为什么我看的资料书里面,不同的平衡树的zip是不一样的?有些是表示左旋,有些是表示右旋,zap也是一样,导致我现在都是按照自己的标准来了。
我在CSDN也发了。https://blog.csdn.net/Liang_Si_FFF/article/details/84190616
【阶梯报告】洛谷P3391【模板】文艺平衡树 splay的更多相关文章
- 洛谷.3391.[模板]文艺平衡树(Splay)
题目链接 //注意建树 #include<cstdio> #include<algorithm> const int N=1e5+5; //using std::swap; i ...
- 【洛谷P3391】文艺平衡树——Splay学习笔记(二)
题目链接 Splay基础操作 \(Splay\)上的区间翻转 首先,这里的\(Splay\)维护的是一个序列的顺序,每个结点即为序列中的一个数,序列的顺序即为\(Splay\)的中序遍历 那么如何实现 ...
- 洛谷.3369.[模板]普通平衡树(Splay)
题目链接 第一次写(2017.11.7): #include<cstdio> #include<cctype> using namespace std; const int N ...
- luoguP3391[模板]文艺平衡树(Splay) 题解
链接一下题目:luoguP3391[模板]文艺平衡树(Splay) 平衡树解析 这里的Splay维护的显然不再是权值排序 现在按照的是序列中的编号排序(不过在这道题目里面就是权值诶...) 那么,继续 ...
- 洛谷.3369.[模板]普通平衡树(fhq Treap)
题目链接 第一次(2017.12.24): #include<cstdio> #include<cctype> #include<algorithm> //#def ...
- 洛谷 P3391 模板Splay
#include<bits/stdc++.h> using namespace std; #define maxn 200000 int read() { ,w=; ;ch=getchar ...
- 【洛谷P3369】普通平衡树——Splay学习笔记(一)
二叉搜索树(二叉排序树) 概念:一棵树,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值: 它的左.右子树也分别为二叉搜索树 ...
- 洛谷 P3391 【模板】文艺平衡树(Splay)
题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1, ...
- [洛谷P3391] 文艺平衡树 (Splay模板)
初识splay 学splay有一段时间了,一直没写...... 本题是splay模板题,维护一个1~n的序列,支持区间翻转(比如1 2 3 4 5 6变成1 2 3 6 5 4),最后输出结果序列. ...
随机推荐
- HTML DOM Table 对象
Table 对象 Table 对象代表一个 HTML 表格. 在 HTML 文档中 <table> 标签每出现一次,一个 Table 对象就会被创建. Table 对象集合 集合 描述 c ...
- vijos - P1077克隆龙 (找规律 + 指数型母函数 + python)
P1077克隆龙 Accepted 标签:[显示标签] 描写叙述 如今龙的克隆已成为可能,龙基因由ACTG字母组成,而龙的基因有例如以下特点: 1.A在基因中的出现为偶数次(包含0): 2.C的情况也 ...
- 容器HashSet原理(学习)
一.概述 使用HashMap存储,非线程安全: 二.实现 HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调 ...
- 【Silverlight】Bing Maps学习系列(五):绘制多边形(Polygon)图形(转)
[Silverlight]Bing Maps学习系列(五):绘制多边形(Polygon)图形 Bing Maps Silverlight Control支持用户自定义绘制多边形(Polygon)图形, ...
- 用C语言实现websocket服务器
Websocket Echo Server Demo 背景 嵌入式设备的应用开发大都依靠C语言来完成,我去研究如何用c语言实现websocket服务器也是为了在嵌入式设备中实现一个ip camera的 ...
- git删除远程分支和本地分支以及更改本地和分支名字
问题描述: 当我们集体进行项目时,将自定义分支push到主分支master之后,如何删除远程的自定义分支呢 问题解决: (1)使用命令git branch -a 查看所有分支 注: 其中,remote ...
- 代码中特殊的注释技术——TODO、FIXME和XXX的用处 (转载)
转自:http://blog.csdn.net/reille/article/details/7161942 作者:reille 本博客网址:http://blog.csdn.net/reille/, ...
- 10.11 NOIP模拟题(1)
/* 离散化 差分 */ #include<bits/stdc++.h> #define N 4000007 using namespace std; int n,ans; int tmp ...
- 例题 5-1 STL
Raju and Meena love to play with Marbles. They have got a lot of marbles with numbers written on the ...
- oozie timezone时区配置
cloudera oozie默认时区是UTC,在开发oozie任务时必须在期望执行的时间上减去8小时,很不习惯.记录下修改时区的配置操作. 1. cloudera oozie配置—>Oozie ...