Splay 区间操作
据大佬说,\(Splay\)是序列操作之王。\(Splay\)是一种平衡树,通过伸展(\(Splay\)),在不改变中序遍历的前提下变换根的位置,从而快速的进行序列操作
\(Splay\)最常见的序列操作是序列反转了:给定一段区间\([L,R]\),要求反转这一段区间
一次\(Splay\)操作复杂度:均摊\(O(\log\ N)\)
一般情况下:我们对一段区间这样操作:选定\(L - 1\)这个节点,Splay到\(root\),因为\(R\)在\(L\)右边,所以现在\(R\)一定在根(\(L- 1\))的右子树内。
此时我们再选定\(R + 1\)这个节点,将其Splay到根的右儿子处,因为满足BST性质(这里满足BST的不是值的大小而是区间的编号大小),\(R + 1\)这一节点的左子树就是需要操作的区间(依据BST,这个子树的内所有节点编号比\(R + 1\)小,比\(L - 1\)大,即\(\in[L,R]\) ).
于是我们参考线段树的\(lazy\)属性,让这个节点代表一整棵树,打上标记需要时再具体修改即可。
几个基本操作
\(Splay\)
若一条折线则转两次自己,直线则转完爸爸再转自己,要是还差一个点到目标则只转一次,以便使\(Splay\)保持平衡(这里的平衡和Treap的平衡不一样)
bool lor(int id){return id == ch[fa[id]][0] ? 0 : 1;}
void spin(int id){
int F = fa[id], d = lor(id);//爸爸,和爸爸的关系
fa[id] = fa[F];//夺取爸爸的权威,(原则:先平等在贬职)
if(fa[F])ch[fa[F]][lor(F)] = id;//接受爷爷的认可 && 保证0号点没有儿子
fa[F] = id;//现在我是爸爸的爸爸了
ch[F][d] = ch[id][d ^ 1];//爸爸的新儿子
if(ch[F][d])fa[ch[F][d]] = F;//保证0号点没有爸爸
ch[id][d ^ 1] = F;//爸爸比我大,去另一边
pushup(F), pushup(id);
}
void splay(int id, int goal){//操作节点和目标节点的爸爸
while(fa[id] != goal){//直到爸爸是目标节点的爸爸为止(成为目标为止)
int F = fa[id];
if(fa[F] == goal)spin(id);//爷爷是目标爸爸,则目标就为爸爸
else if(lor(id) ^ lor(F))spin(id),spin(id);//折线两次自己
else spin(F),spin(id);//直线先爸爸在自己
}
if(!goal)root = id;//由于我们判断的是目标的爸爸,所以不会动根,当目标是根的爸爸(虚节点)的时候变一下根
}
\(find \ \&\ insert\)
注意\(find\)在区间操作中返回编号和\(insert\)完\(Splay\)到\(root\)以维护\(Splay\)的平衡性即可
int find(int id, int rank){
pushdown(id);
if(size[ch[id][0]] >= rank)return find(ch[id][0], rank);
else if(size[ch[id][0]] + 1 == rank)return id;//注意返回编号而不是值
else return find(ch[id][1], rank - size[ch[id][0]] - 1);
}
void insert(int &id, int F, int v){
if(!id){id = New(v, F);splay(id, 0);return ;}//每插入一个点要把他Splay到根
if(v < val[id])insert(ch[id][0], id, v);
else insert(ch[id][1], id, v);
}
\(Reverse\)
注意添加哨兵节点后每个节点 $ + 1$即可
void Reverse(int l,int r){
int x = find(root, l),y = find(root, r + 2);//哨兵节点,所以区间加一
splay(x,0);splay(y,root);
lazy[ch[ch[root][1]][0]] ^= 1;
}
P3391 【模板】文艺平衡树(Splay)
题目背景
这是一道经典的Splay模板题——文艺平衡树。
题目描述
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1
输入输出格式
输入格式:
第一行为n,m n表示初始序列有n个数,这个序列依次是 (1,2, \cdots n-1,n)(1,2,⋯n−1,n) m表示翻转操作次数
接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r \leq n 1≤l≤r≤n
输出格式:
输出一行n个数字,表示原始序列经过m次变换后的结果
一棵二叉树中,翻转即为树内所有左右节点翻转
在这一题中,我们需要翻转区间。参考上面的分析,我们可以给树上节点打上一个\(lazy\)来表示区间是否应该翻转,若有翻转,则交换自己的左右儿子,同时下放懒标记即可
有几点注意事项:
区间修改万一包括最左(右)节点,我们没有更左(右)节点能\(Splay\)到根或者根的右儿子节点,所以我们需要增加两个哨兵节点表示\(-Inf\)和\(INF\)来防止RE。
我的\(Splay\)和别人的不太一样,有些人Splay的第二个参数是目标节点,而我那个版本是目标节点的爸爸,为了防止出现bug,\(0\)号节点(根的爸爸,也称为虚点)不能有儿子或父亲,所以维护儿子父亲的时候记的判断一下
这个版本的\(Splay\)时没有涉及根节点的交换转移,所以每次\(Splay\)玩需要判断一下:若目标节点是根节点,则手动换一下根
Code
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<climits>
typedef long long LL;
using namespace std;
int RD(){
int out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 100019,INF = 1e9;
int ch[maxn][2];
int val[maxn], lazy[maxn];
int size[maxn];
int fa[maxn];
int root, tot;
int New(int F,int v){
fa[++tot] = F;
size[tot] = 1;
lazy[tot] = 0;
val[tot] = v;
return tot;
}
void pushup(int id){size[id] = size[ch[id][0]] + size[ch[id][1]] + 1;}
void pushdown(int id){
if(lazy[id]){
swap(ch[id][0], ch[id][1]);
lazy[ch[id][0]] ^= 1;
lazy[ch[id][1]] ^= 1;
lazy[id] = 0;
}
}
bool lor(int id){return id == ch[fa[id]][0] ? 0 : 1;}
void spin(int id){
int F = fa[id], d = lor(id);//爸爸,和爸爸的关系
fa[id] = fa[F];//夺取爸爸的权威,(原则:先平等在贬职)
if(fa[F])ch[fa[F]][lor(F)] = id;//接受爷爷的认可 && 保证0号点没有儿子
fa[F] = id;//现在我是爸爸的爸爸了
ch[F][d] = ch[id][d ^ 1];//爸爸的新儿子
if(ch[F][d])fa[ch[F][d]] = F;//保证0号点没有爸爸
ch[id][d ^ 1] = F;//爸爸比我大,去另一边
pushup(F), pushup(id);
}
void splay(int id, int goal){//操作节点和目标节点的爸爸
while(fa[id] != goal){//直到爸爸是目标节点的爸爸为止(成为目标为止)
int F = fa[id];
if(fa[F] == goal)spin(id);//爷爷是目标爸爸,则目标就为爸爸
else if(lor(id) ^ lor(F))spin(id),spin(id);//折线两次自己
else spin(F),spin(id);//直线先爸爸在自己
}
if(!goal)root = id;//由于我们判断的是目标的爸爸,所以不会动根,当目标是根的爸爸(虚节点)的时候变一下根
}
int find(int id, int rank){
pushdown(id);
if(size[ch[id][0]] >= rank)return find(ch[id][0], rank);
else if(size[ch[id][0]] + 1 == rank)return id;
else return find(ch[id][1], rank - size[ch[id][0]] - 1);
}
void insert(int &id, int F, int v){
if(!id){id = New(v, F);splay(id, 0);return ;}//每插入一个点要把他Splay到根
if(v < val[id])insert(ch[id][0], id, v);
else insert(ch[id][1], id, v);
}
void Reverse(int l,int r){
int x = find(root, l),y = find(root, r + 2);//哨兵节点,所以区间加一
splay(x,0);splay(y,root);
lazy[ch[ch[root][1]][0]] ^= 1;
}
int num,nr;
int main(){
num = RD();nr = RD();
for(int i = 0;i <= num + 1;i++)insert(root, 0, i);
for(int i = 1;i <= nr;i++){
int l = RD(),r = RD();
Reverse(l,r);
}
for(int i = 1;i <= num;i++)printf("%d ",val[find(root,i + 1)]);
return 0;
}
Splay 区间操作的更多相关文章
- P2596 [ZJOI2006]书架 && Splay 区间操作(三)
P2596 [ZJOI2006]书架 题目描述 小T有一个很大的书柜.这个书柜的构造有些独特,即书柜里的书是从上至下堆放成一列.她用1到n的正整数给每本书都编了号. 小T在看书的时候,每次取出一本书, ...
- P2042 [NOI2005]维护数列 && Splay区间操作(四)
到这里 \(A\) 了这题, \(Splay\) 就能算入好门了吧. 今天是个特殊的日子, \(NOI\) 出成绩, 大佬 \(Cu\) 不敢相信这一切这么快, 一下子机房就只剩我和 \(zrs\) ...
- HDU 1754 I Hate It (Splay 区间操作)
题目大意 维护一个序列,支持两种操作 操作一:将第x个元素的值修改为y 操作二:询问区间[x,y]内的元素的最大值 解题分析 splay的区间操作,事先加入两个编号最小和最大的点防止操作越界. 具体的 ...
- 「BZOJ1251」序列终结者 (splay 区间操作)
题面: 1251: 序列终结者 Time Limit: 20 Sec Memory Limit: 162 MBSubmit: 5367 Solved: 2323[Submit][Status][D ...
- Splay 区间操作(二)
首先基本操作如下: 删除第rank个点 void Remove(int id){//删除第rank个点 rank++; int x = find(root, rank - 1); splay(x, 0 ...
- [bzoj1500][NOI2005 维修数列] (splay区间操作)
Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目. 第2行包含N个数字,描述初始时的数列. 以下M行,每 ...
- Splay 的区间操作
学完Splay的查找作用,发现和普通的二叉查找树没什么区别,只是用了splay操作节省了时间开支. 而Splay序列之王的称号可不是白给的. Splay真正强大的地方是他的区间操作. 怎么实现呢? 我 ...
- HDU 4453:Looploop(Splay各种操作)
http://acm.hdu.edu.cn/showproblem.php?pid=4453 题意:很多种操作:1.add x,将从光标起的 k2 个数全部加上 x:2.reverse,将从光标起的 ...
- 算法模板——splay区间反转 2
实现功能:同splay区间反转 1(基于BZOJ3223 文艺平衡树) 这次改用了一个全新的模板(HansBug:琢磨了我大半天啊有木有),大大简化了程序,同时对于splay的功能也有所完善 这里面没 ...
随机推荐
- ffmpe安装
原文:https://www.jianshu.com/p/905df3d9e753 下载安装 下载最新源码包并解压 $ wget http://ffmpeg.org/releases/ffmpeg-3 ...
- USACO 1.3.3 Calf Flac(Manacher算法)
Description 据说如果你给无限只母牛和无限台巨型便携式电脑(有非常大的键盘),那么母牛们会制造出世上最棒的回文.你的工作就是去寻找这些牛制造的奇观(最棒的回文). 在寻找回文时不用理睬那些标 ...
- mysql 查询数据库或某张表有多大(字节)
转载:https://www.cnblogs.com/diandiandidi/p/5582309.html 1.要查询表所占的容量,就是把表的数据和索引加起来就可以了 select sum(DATA ...
- 搜索引擎Elasticsearch REST API学习
Elasticsearch为开发者提供了一套基于Http协议的Restful接口,只需要构造rest请求并解析请求返回的json即可实现访问Elasticsearch服务器.Elasticsearch ...
- Java的一些细节问题
一.Java求余%的结果符号取决于除数的符号位:小数也可以求余,余数仍为小数. package com.test; public class Test { /** * @author 容杰龙 */ p ...
- 【beta】Scrum站立会议第4次....11.6
小组名称:nice! 组长:李权 成员:于淼 刘芳芳韩媛媛 宫丽君 项目内容:约跑app(约吧) 时间: 12:00——12:30 地点:传媒西楼220室 本次对beta阶段的需求进行更新如下: ...
- 【Linux笔记】GRUB配置与应用,启动故障分析解决。
一.GRUB启动位置 GRUB是现今大多数Linux系统采用的自举程序,这里先来看一下Linux的程序顺序: 执行顺序 动作 固件Firmware(CMOS/BIOS) → POST(Pwer ...
- 【uoj#174】新年的破栈 贪心
题目描述 给你一个长度为 $n$ 的序列和一个空的双端队列,每次进行3种操作种的一种: 1.将序列中编号最小的数加入到双端队列的队尾:2.从双端队列的队尾取出一个数:3.从双端队列的队头取出一个数. ...
- 【数据库_Mysql】MySQL—修改表时给表添加联合主键约束
添加语法如下: “ALTER TABLE table_name ADD CONSTRAINT pk_table_name PRIMARY KEY(列名1,列名2):” [示例1]假设订房信息表(O ...
- debug - vue中通过ajax获取数据时,如何避免绑定的数据中出现property of undefined错误
因为获取服务器是异步的,所以 vue 先绑定数据. 如果 ??? 是通过 ajax 异步获取的,在获取之前,???是未定义的.此时在外面的标签上添加一个 v-if="???" 可以 ...