• SPLAY,LCT学习笔记(一)


    写了两周数据结构,感觉要死掉了,赶紧总结一下,要不都没学明白。

    SPLAY专题:

    例:NOI2005 维修数列

    典型的SPLAY问题,而且综合了SPLAY常见的所有操作,特别适合新手入门学习(比如我这种蒟蒻)

    题目要求很多,我们一步一步来分析

    首先,区间翻转是SPLAY一个很基础的操作,我们以他为基础分析这个SPLAY

    例:luogu文艺平衡树

    题目:读入一个序列,进行多次区间翻转操作,请你输出操作后的序列

    我们怎么处理呢?

    首先要明确一点,就是SPLAY是可以当做区间树来使用的(就像线段树一样)

    所以,我们首先把原来读进来的序列建起一个SPLAY,建树方法类似于线段树

    void buildtree(int l,int r,int f)
    {
    	int mid=(l+r)>>1;
    	if(l==r)
    	{
    		tree[l].val=a[l];
    		tree[l].huge=1;
    		tree[l].ttag=0;
    		tree[l].fa=f;
    		tree[l].lson=tree[l].rson=0;
    	}
    	if(l<mid)
    	{
    		buildtree(l,mid-1,mid);
    	}
    	if(r>mid)
    	{
    		buildtree(mid+1,r,mid);
    	}
    	tree[mid].val=a[mid];
    	tree[mid].fa=f;
    	tree[mid].ttag=0;
    	update(mid);
    	if(mid<f)
    	{
    		tree[f].lson=mid;
    	}else
    	{
    		tree[f].rson=mid;
    	}
    }

    注意到其中有一个ttag,这是翻转区间的标记,暂时先不用关心

    这样我们就建起了一个SPLAY

    然后我们把SPLAY的根置成整个区间的中点即可

    还有一个要点,就是SPLAY中一个节点既是序列中的一个位置,也是树中一个根节点,换言之,如果你建立了一棵SPLAY,根节点对应的是原序列中4位置,那么他的左子树就要维护1-3位置,右子树要维护5-7位置,而4位置交给根节点自己,不能放进子树中

    这也是他与线段树少数的区别。

    所以如果我们有一个序列1,2,3,4,5,6,7

    建起的SPLAY大概会长这个样子

    接下来,我们来尝试做一下区间翻转:

    假设我要翻转区间[2,4],我要怎么办呢?

    现在我希望找到一棵子树,使得这棵子树正好对应了[2,4]这个区间,然后我们把这个区间整体打上标记就可以了

    怎么找?

    这才是SPLAY真正要解决的问题

    最后我们得出了一个结论:假设我们要翻转区间[2,4],那么我们首先把1转到SPLAY的根上(别忘了,这是一棵伸展树,是可以旋转的)

    那么旋转规则和treap类似,最后大概长这样吧:

    至于怎么转的,暂时还不必关心,一会就提到

    接下来,我们把5节点旋转到根节点的右儿子处,长这样:

     (原谅本蒟蒻的画图技术)

    发现什么了吗?

    当5旋转到右子节点以后,你所需要的区间[2,4]自然出现在了5的左子树上!

    这样我们只对这棵树打一个标记就好

    总结:如果我想修改区间[l,r],那么我只需将节点l-1转至SPLAY的根节点上,将节点r+1转至根节点的右儿子上,那么这个右儿子的左子树就是你想要的区间[l,r]!

    至于证明,由于SPLAY本质是一个二叉搜索树,所以要满足搜索树的性质(这也是所有基于二叉搜索树变形出的数据结构所有旋转,伸展等等诡异操作的原理)

    而搜索树的性质:左子树<根<右子树

    那就好办啦,把要修改的区间前驱放到根上,这样整个区间一定在根的右子树内

    再把区间的后继放到根的右子树上,这样整个区间一定在这个节点的左子树内

    这样不就分出来这段区间了吗。

    等等,这里面是不有点问题?

    是啊,如果l=1呢?

    这样的话,l-1就为0了,而在SPLAY里,找的到这样一个点吗?

    似乎找不到诶

    或者,如果r=n的话,那r+1就等于n+1了,好像同样不存在诶

    那我们怎么玩?

    一句话:插入两个空节点!

    为什么呢?

    假如我们将整个区间由[1,n]变为[2,n+1],然后加入1和n+2两个空节点,这样不就可以实现上面所说的所有要求了吗?

    这就很妙啦

    于是,我们来解决一下最后这个问题:怎么旋转?

    结论如下(这里使用双旋splay,常数小一些)

    void rotate(int st,int &ed)
    {
    	int ltyp;
    	int v1=tree[st].fa;
    	int v2=tree[v1].fa;
    	if(tree[v1].rson==st)
    	{
    		ltyp=1;
    	}else
    	{
    		ltyp=0;
    	}
    	if(v1==ed)
    	{
    		ed=st;
    	}else
    	{
    		if(tree[v2].lson==v1)
    		{
    			tree[v2].lson=st;
    		}else
    		{
    			tree[v2].rson=st;
    		}
    	}
    	if(ltyp)
    	{
    		tree[tree[st].lson].fa=v1;
    		tree[v1].fa=st;
    		tree[v1].rson=tree[st].lson;
    		tree[st].lson=v1;
    		tree[st].fa=v2;
    	}else
    	{
    		tree[tree[st].rson].fa=v1;
    		tree[v1].fa=st;
    		tree[v1].lson=tree[st].rson;
    		tree[st].rson=v1;
    		tree[st].fa=v2;
    	}
    	update(v1);
    	update(st);
    }
    void splay(int st,int &ed)
    {
    	while(st!=ed)
    	{
    		int v1=tree[st].fa,v2=tree[v1].fa;
    		if(v1!=ed)
    		{
    			if((tree[v1].lson==st&&tree[v2].lson!=v1)||(tree[v1].rson==st&&tree[v2].rson!=v1))
    			{
    				rotate(st,ed);
    			}else
    			{
    				rotate(v1,ed);
    			}
    		}
    		rotate(st,ed);
    	}
    }

    双旋,按类似treap的方法反复旋转即可

    最后,每次操作的时候都需要下传标记,然后想输出的话对这棵树进行中序遍历即可

    全代码(文艺平衡树):

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    #define ls tree[rt].lson
    #define rs tree[rt].rson
    using namespace std;
    struct SPLAY
    {
    	int lson;
    	int rson;
    	int val;
    	int fa;
    	bool ttag;
    	int huge;
    }tree[100005];
    int a[100005];
    int rot;
    int n,m;
    void update(int rt)
    {
    	tree[rt].huge=tree[ls].huge+tree[rs].huge+1;
    }
    void pushdown(int rt)
    {
    	if(tree[rt].ttag)
    	{
    		tree[rt].ttag=0;
    		tree[ls].ttag^=1;
    		tree[rs].ttag^=1;
    		swap(tree[ls].lson,tree[ls].rson);
    		swap(tree[rs].lson,tree[rs].rson);
    	}
    }
    void buildtree(int l,int r,int f)
    {
    	int mid=(l+r)>>1;
    	if(l==r)
    	{
    		tree[l].val=a[l];
    		tree[l].huge=1;
    		tree[l].ttag=0;
    		tree[l].fa=f;
    		tree[l].lson=tree[l].rson=0;
    	}
    	if(l<mid)
    	{
    		buildtree(l,mid-1,mid);
    	}
    	if(r>mid)
    	{
    		buildtree(mid+1,r,mid);
    	}
    	tree[mid].val=a[mid];
    	tree[mid].fa=f;
    	tree[mid].ttag=0;
    	update(mid);
    	if(mid<f)
    	{
    		tree[f].lson=mid;
    	}else
    	{
    		tree[f].rson=mid;
    	}
    }
    int findf(int rt,int v)
    {
    	pushdown(rt);
    	if(tree[ls].huge+1==v)
    	{
    		return rt;
    	}else if(tree[ls].huge>=v)
    	{
    		return findf(ls,v);
    	}else
    	{
    		return findf(rs,v-1-tree[ls].huge);
    	}
    }
    void rotate(int st,int &ed)
    {
    	int ltyp;
    	int v1=tree[st].fa;
    	int v2=tree[v1].fa;
    	if(tree[v1].rson==st)
    	{
    		ltyp=1;
    	}else
    	{
    		ltyp=0;
    	}
    	if(v1==ed)
    	{
    		ed=st;
    	}else
    	{
    		if(tree[v2].lson==v1)
    		{
    			tree[v2].lson=st;
    		}else
    		{
    			tree[v2].rson=st;
    		}
    	}
    	if(ltyp)
    	{
    		tree[tree[st].lson].fa=v1;
    		tree[v1].fa=st;
    		tree[v1].rson=tree[st].lson;
    		tree[st].lson=v1;
    		tree[st].fa=v2;
    	}else
    	{
    		tree[tree[st].rson].fa=v1;
    		tree[v1].fa=st;
    		tree[v1].lson=tree[st].rson;
    		tree[st].rson=v1;
    		tree[st].fa=v2;
    	}
    	update(v1);
    	update(st);
    }
    void splay(int st,int &ed)
    {
    	while(st!=ed)
    	{
    		int v1=tree[st].fa,v2=tree[v1].fa;
    		if(v1!=ed)
    		{
    			if((tree[v1].lson==st&&tree[v2].lson!=v1)||(tree[v1].rson==st&&tree[v2].rson!=v1))
    			{
    				rotate(st,ed);
    			}else
    			{
    				rotate(v1,ed);
    			}
    		}
    		rotate(st,ed);
    	}
    }
    int split(int st,int ed)
    {
    	int v1=findf(rot,st-1);
    	int v2=findf(rot,ed+1);
    	splay(v1,rot);
    	splay(v2,tree[v1].rson);
    	return tree[v2].lson;
    }
    void reverse(int st,int ed)
    {
    	int v1=split(st,ed);
    	tree[v1].ttag^=1;
    	swap(tree[v1].lson,tree[v1].rson);
    	update(tree[v1].fa);
    	update(tree[tree[v1].fa].fa);
    } 
    void print(int rt)
    {
    	if(!rt)
    	{
    		return;
    	}
    	pushdown(rt);
    	print(ls);
    	printf("%d ",tree[rt].val);
    	print(rs);
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    	{
    		a[i+1]=i;
    	}
    	buildtree(1,n+2,0);
    	rot=(n+3)>>1;
    	for(int i=1;i<=m;i++)
    	{
    		int st,ed;
    		scanf("%d%d",&st,&ed);
    		reverse(st+1,ed+1);
    	}
    	int v1=split(2,n+1);
    	print(v1);
    	return 0;
    }

    这样SPLAY的基础知识就完成了

    剩下的留待下一篇更新

  • 相关阅读:
    silverlight 自定义 鼠标 双击事件
    silverlight 常用特效
    silverlight 碰撞检测
    silverlight的自定义依赖属性
    在程序代码中集成跨域服务文件
    Silverlight动画基础
    silverlight 虚线框
    建立纯代码的silverlight项目
    silverlight字符串加密之二
    silverlight3 加载其他xap
  • 原文地址:https://www.cnblogs.com/zhangleo/p/10764217.html
Copyright © 2020-2023  润新知