• 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;
    	}
    
  • 相关阅读:
    获取缓存文件大小并清理 By HL
    iOS 模糊、精确搜索匹配功能方法总结 By HL
    让 iOS 设备 “说出” 你想说的话!! #DF
    自定义索引--秀清
    云端iclound使用-陈棚
    IM开发之Socket通信开源类库CocoaAsyncSocket
    iOS App 架构文章推荐
    IM开发通信协议基础知识(一)---TCP、UDP、HTTP、SOCKET
    [手游项目5]windows获得当前进程名
    【软件安装】c++11安装
  • 原文地址:https://www.cnblogs.com/Tony-Double-Sky/p/9299555.html
Copyright © 2020-2023  润新知