zoukankan      html  css  js  c++  java
  • 文艺平衡树

    文艺平衡树

    之前我用分块乱搞过文艺平衡树,今天一起来看看如何用平衡树实现文艺平衡树。

    题目描述:

    给你长度为 (n) 的序列 (A) ,初始时 (A_i=i) ,有 (m) 次操作,每次对给定区间 ([l,r]) 进行翻转。

    要求输出 (m) 次操作以后的序列。

    实现文艺平衡树有三种思路:Splay,FHQ-Treap ,当然还有分块(划掉

    因为我太弱,不会 FHQ-Treap 所以使用 Splay 来实现文艺平衡树。

    Part 1 实现思路

    由于我们需要输出元素反转后的顺序,而对元素本身的值不关心,这启示我们要对元素下标而不是元素的值建立 Splay 。

    假设现在对元素下标建立了 Splay ,即这个 Splay 的中序遍历的第 (i) 个数代表 (A_i)

    考虑如何进行“翻转”操作:想要翻转,先得找到翻转对象,这样才能有的放矢,假设要对区间 ([l,r]) 进行翻转操作:


    (图自皎月半撒花的博客,侵删)

    可以把中序遍历第 (l-1) 个数伸展到根,第 (r+1) 个数伸展到根的右儿子(实际上就是第 (l-1) 名和第 (r+1) 名,利用 Splay 的查排名功能可以方便找到这两个节点),根据中序遍历 左->中->右 的性质,三角形所示子树就是翻转区间了。

    把当前区间确定下来之后,我要开始进行翻转操作。按照中序遍历访问 Splay 的话,顺序是 左->中->右 。如果要翻转一个区间,顺序就要变成 右->中->左 。在不考虑变化遍历顺序的情况下,我们应该交换左右子树。即下图:

    假如 u 及 u 的子树代表翻转区间,则需要把 u 及其子树内所有儿子的左右子树交换,不能只换 u ,道理很显然。但是如果我们每次都直接暴力交换 u 及 u 的子树的话,复杂度显然 (O(n))

    需要考虑优化,之前写分块和线段树的时候,曾经用到“打标记”的策略,每次修改只打标记,择机再下传这个标记。那么 Splay 可以打标记吗?

    完全可以。

    每次翻转操作,经过伸展后确定了反转区间构成的子树的根,把要翻转的子树的根节点打上一个标记,表示以这个节点为根的子树应该被翻转。在此之后,假如要旋转这个节点,就把翻转标记先下传到左右儿子,然后交换左右儿子,最后再旋转。和异或标记一样,如果一个区间翻转两次就相当于没有翻转,已经有标记的节点再打一个标记相当于没有标记。

    至于其他操作,和普通 Splay 无二。

    Part 2 Code

    这里有一个实现细节:如果要翻转区间 ([1,n]) 的话,需要查 1 的前驱和 n 的后继,但是初始建树的时候 1 没有前驱,n 没有后继,这样在查的时候会把 null 转到根上导致 RE 。这里我选择插入 n+2 个数,把 x+1 当作 x 进行操作,输出时再减掉 1 就行了,具体见代码。

    #include<algorithm>
    #include<iostream>
    #include<cstdlib>
    #include<iomanip>
    #include<cstring>
    #include<utility>
    #include<cstdio>
    #include<queue>
    #include<stack>
    #include<ctime>
    #include<cmath>
    #include<list>
    #include<set>
    #include<map>
    
    namespace zhang_tao{
    	#define inf 0x7f7f7f7f
    	#define ll long long
      template <typename _T>
      inline _T const& read(_T &x){
        x=0;int fh=1;
        char ch=getchar();
        while(!isdigit(ch)){
    
          if(ch=='-')
            fh=-1;
          ch=getchar();
        }
        while(isdigit(ch)){
          x=x*10+ch-'0';
          ch=getchar();
        }
        return x*=fh;
      }
      void write(int x){
        if(x<0) putchar('-'),x=-x;
        if(x>9) write(x/10);
        putchar(x%10+48);
      }
      inline int _gcd(const int x,const int y){
    		return y?_gcd(y,x%y):x;
    	}
    	inline int _lcm(const int x,const int y){
    		return x*y/_gcd(x,y);
    	}
    	inline int max(int a,int b,int c=-inf){
    		return std::max(std::max(a,b),c);
    	}
    	inline int min(int a,int b,int c=inf){
    		return std::min(std::min(a,b),c);
    	}
    //	inline void swap(int &x,int &y){
    //		x^=y^=x^=y;
    //	}
    } // namespace zhang_tao
    using namespace zhang_tao;
    
    int n,m;
    
    struct Splay{
    	int value,tag,size;
    	Splay *son[2],*fa;
    };
    
    Splay *root,*null;
    
    inline void swap(Splay* &x,Splay* &y){
    	Splay *tmp=x;x=y;y=tmp;
    }//交换两个指针
    
    inline void update(Splay *node){
    	node->size=node->son[0]->size+node->son[1]->size+1;
    }//更新节点子树信息
    
    inline bool get_which(Splay *node){
    	return node->fa->son[1]==node;
    } //返回是左儿子还是右儿子
    
    inline void push_down(Splay *node){
    	if(node->tag){
    		node->son[0]->tag^=1;
    		node->son[1]->tag^=1;//给两个儿子打标记
            //其实这里应该特判,不然会修改null的标记,不过没啥影响就算了
    		node->tag=0;//清除自身标记
    		swap(node->son[0],node->son[1]);//交换左右子树
    	}//下传node节点的标记
    }
    
    void rotate(Splay *node){
    	Splay *fa=node->fa;
    	Splay *grandfa=fa->fa;
    	int which_son=get_which(node);
    	if(grandfa!=null)
    		grandfa->son[get_which(fa)]=node;
    	node->fa=grandfa;
    	fa->son[which_son]=node->son[which_son^1];
    	node->son[which_son^1]->fa=fa;	
    	node->son[which_son^1]=fa;
    	fa->fa=node;
    	update(fa),update(node); //和普通Splay的rotete()函数一样
    }
    
    void splay(Splay *node,Splay *target){
    	while(node->fa!=target){
    		Splay *fa=node->fa;
    		Splay *grandfa=fa->fa;
    		if(grandfa!=target)
    			rotate( get_which(fa)^get_which(node) ? node : fa );
    		rotate(node); 
    	}
    	if(target==null)
    		root=node; 
    }//和普通Splay的splay()函数一样
    
    void insert(int x){
    	Splay *node=root,*fa=null;
    	while(node!=null){
    		fa=node;
    		node=node->son[x>node->value];
    	}
    	node=new Splay;
    	node->fa=fa;
    	if(fa!=null) fa->son[x>fa->value]=node;
    	node->son[0]=node->son[1]=null;
    	node->tag=0;
    	node->size=1;
    	node->value=x;
    	splay(node,null);
    }//和普通Splay的insert()函数一样
    //这里可以 O(n) 直接拔起一棵Splay来,但是我比较懒,直接insert了
    
    Splay* get_kth(int k){
    	Splay *node=root;
    	while(1){
    		push_down(node);//这里下传标记,之后伸展的时候就不用下传了
    		if(node->son[0]->size>=k)
    			node=node->son[0];
    		else{
    			k-=(node->son[0]->size+1);
    			if(k==0) return node;
    			else node=node->son[1];//剩下的和普通查k小一样
    		}
    	} 
    }
    
    void reverse(int L,int R){
    	Splay *pre=get_kth(L),*back=get_kth(R+2);
        //实际上找L-1,R+1,因为把n+1当成n,变成了查L和R+2
    	splay(pre,null),splay(back,pre);
    	root->son[1]->son[0]->tag^=1;
    }//上述方法伸展、打标记
    
    void output(Splay *node){
    	push_down(node);//别忘了下传标记
    	if(node->son[0]!=null) output(node->son[0]);
    	if(node->value>1 && node->value<n+2) write(node->value-1),putchar(' ');
        //因为多插入了两个点,输出值-1
    	if(node->son[1]!=null) output(node->son[1]); 
    }//中序遍历输出
    
    signed main(){
    	null=new Splay;
    	null->fa=null->son[0]=null->son[1]=NULL;
    	null->size=null->tag=null->value=0;//初始化null节点
    	root=null;
    	read(n),read(m);
    	for(int i=1;i<=n+2;++i)
    		insert(i);
       	//如果反转的是[1,n]的话,需要找到1的前驱,n的后继,这里多插入两个点,然后把n+1当成n,不然会RE
    	for(int i=1,l,r;i<=m;++i){
    		read(l),read(r);
    		reverse(l,r);//翻转
    	}
    	output(root);//从根中序遍历输出
    	return 0; 
    }
    

    Part 3 后记

    这写的啥啊,自己都看不懂。

    没看明白并且毫无帮助?那就对了。

    繁华尽处, 寻一静谧山谷, 筑一木制小屋, 砌一青石小路, 与你晨钟暮鼓, 安之若素。
  • 相关阅读:
    Flask + vue 前后端分离的 二手书App
    Kafka 0.10.0.1 consumer get earliest partition offset from Kafka broker cluster
    Kafka topic Schema version mismatch error
    ORM的多表查询详述
    ORM多表操作之创建关联表及添加表记录
    ORM的单表操作
    Django的模板层简介
    Django的视图层简介
    Django中的路由配置简介
    Django简介及Django项目的创建详述
  • 原文地址:https://www.cnblogs.com/zaza-zt/p/15061435.html
Copyright © 2011-2022 走看看