• [AH2017/HNOI2017]单旋


    题目链接

    https://www.luogu.com.cn/problem/P3721

    做法

    好神的题目!!!

    我们重新修改一下链的定义,一条链为(a_1,a_2,a_3,a_4,a_5...),仅当(a_{i}(∀i>1))(a_{i-1})的左儿子,或者右儿子。

    不难发现,对于一条链,链底旋到链顶,其实只会把链底和其的某个儿子带到根节点,其余不变。
    在这里插入图片描述
    就在我思考不是链的情况,膜了一下题解,发现最大最小值到根节点一定是一条链!!!

    且其最多只有一个儿子,要么左儿子,要么右儿子,且这两个儿子会在第一次旋转的时候过继给其父亲,并不会跟随其带动根当中。

    所以每次(splay)只需要用线段树统计深度(怎么维护后面说),用(rt)储存根节点,然后记录一下(fa)(son)数组即可反正每次最多修改几个,暴力修改即可

    还需要记住平衡树有个非常妙的性质,就是如果把原数组排序离散化,那么一个点的子树的中序遍历刚好就是这个数组上连续的一段,再看操作,例如:删除掉(max)值,其实就是把(max)的左儿子(也是其唯一的儿子)的子树深度全部(-1),那么对应在排序离散后的数组,其实就是这个子树所对应的连续的一段,直接用线段树维护每个位置的深度即可。

    代码如下(貌似我是连(x)一同进行深度修改的):

    inline  void  del_dian(int  x)//x不能是根节点 
    {
    	int  f=fa[x],w=son[f][0]==x?0:1;
    	int  l=0,r=0;//修改的范围
    	if(w==0)l=x,r=f-1;//最小值 
    	else  l=f+1,r=x;//最大值 
    	change1(1,1,qlen,l,r,-1);//深度减少1 
    	fa[x]=0;
    	if(son[x][0])fa[son[x][0]]=f,son[f][w]=son[x][0];
    	else  fa[son[x][1]]=f,son[f][w]=son[x][1];
    }
    

    当然,可能有人会问啦,每次插入一个数字,你怎么用线段树动态维护数组排序和离散化之后的数组呢?其实我们只要把最终的数组排序离散化一下,然后对于没出现过的数字不管他,然后数字出现单点修改即可。(因为代价只用单点查询)

    但是插入到底会插入在哪个数字下面呢?

    不难发现,要么前驱,要么后继(求前驱后继用set实现),这里又分两种情况了,要么一个在另外一个子树之内,要么在不同的子树,分类讨论即可。(只要运用好“把原数组排序离散化,那么一个点的子树的中序遍历刚好就是这个数组上连续的一段”的性质,这个操作应该不难想)

    加入操作:

    inline  void  add(int  k,int  father)
    {
    	fa[k]=father;
    	if(father>k)son[father][0]=k;
    	else  son[father][1]=k;
    }
    inline  int  ins(int  x)
    {
    	fuck.insert(x);++cnt;
    	if(!root){change2(1,1,qlen,x,1);root=x;return  1;}
    	else
    	{
    		set<int>::iterator  pre,hou;
    		pre=fuck.lower_bound(x);
    		hou=fuck.upper_bound(x);
    		
    		int  deppre=999999999,dephou=999999999;
    		if(pre!=fuck.begin())
    		{
    			pre--;
    			deppre=findans(1,1,qlen,*pre);
    		}
    		if(hou!=fuck.end())dephou=findans(1,1,qlen,*hou);
    		int  father;
    		if(deppre<dephou)//前面的节点是我的祖先 
    		{
    			if(!son[*pre][1])add(x,*pre),father=*pre;
    			else  add(x,*hou),father=*hou;
    		}
    		else
    		{
    			if(!son[*hou][0])add(x,*hou),father=*hou;
    			else  add(x,*pre),father=*pre;
    		}
    		if(father==*hou)
    		{
    			change2(1,1,qlen,x,dephou+1);
    			return  dephou+1;
    		}
    		else
    		{
    			change2(1,1,qlen,x,deppre+1);
    			return  deppre+1;
    		}
    	}
    }
    

    所以,总的来说,时间复杂度就是(O(nlogn))

    #include<cstdio>
    #include<cstring>
    #include<set>
    #include<algorithm>
    #define  N  110000
    #define  NN  210000
    using  namespace  std;
    typedef  long  long  LL;
    int  n;
    struct  node
    {
    	int  l,r,lazy;
    }tr[NN];int  len,rt;
    inline  void  pushlazy(int  x,int  k){tr[x].lazy+=k;}
    inline  void  downdata(int  x)
    {
    	if(tr[x].lazy)
    	{
    		pushlazy(tr[x].l,tr[x].lazy);pushlazy(tr[x].r,tr[x].lazy);
    		tr[x].lazy=0;
    	}
    }
    inline  void  bt(int  l,int  r)
    {
    	int  now=++len;
    	if(l<r)
    	{
    		int  mid=(l+r)>>1;
    		tr[now].l=len+1;bt(l,mid);
    		tr[now].r=len+1;bt(mid+1,r);
    	}
    	else  tr[now].lazy=0;
    }
    inline  void  change1(int  now,int  l,int  r,int  ll,int  rr,int  k)
    {
    	if(l==ll  &&  r==rr){pushlazy(now,k);return  ;}
    	int  mid=(l+r)>>1;
    	downdata(now);
    	if(rr<=mid)change1(tr[now].l,l,mid,ll,rr,k);
    	else  if(mid<ll)change1(tr[now].r,mid+1,r,ll,rr,k);
    	else  change1(tr[now].l,l,mid,ll,mid,k),change1(tr[now].r,mid+1,r,mid+1,rr,k);
    }
    inline  void  change2(int  now,int  l,int  r,int  k,int  c)
    {
    	if(l==r){tr[now].lazy=c;return  ;}
    	int  mid=(l+r)>>1;
    	downdata(now);
    	if(k<=mid)change2(tr[now].l,l,mid,k,c);
    	else  change2(tr[now].r,mid+1,r,k,c);
    }
    inline  int  findans(int  now,int  l,int  r,int  k)
    {
    	if(l==r)return  tr[now].lazy;
    	int  mid=(l+r)>>1;
    	downdata(now);
    	if(k<=mid)return  findans(tr[now].l,l,mid,k);
    	else  return  findans(tr[now].r,mid+1,r,k);
    }
    int  fa[N],son[N][2],root/*根节点*/,cnt,qlen;
    set<int> fuck;
    inline  void  add(int  k,int  father)
    {
    	fa[k]=father;
    	if(father>k)son[father][0]=k;
    	else  son[father][1]=k;
    }
    inline  int  ins(int  x)
    {
    	fuck.insert(x);++cnt;
    	if(!root){change2(1,1,qlen,x,1);root=x;return  1;}
    	else
    	{
    		set<int>::iterator  pre,hou;
    		pre=fuck.lower_bound(x);
    		hou=fuck.upper_bound(x);
    		
    		int  deppre=999999999,dephou=999999999;
    		if(pre!=fuck.begin())
    		{
    			pre--;
    			deppre=findans(1,1,qlen,*pre);
    		}
    		if(hou!=fuck.end())dephou=findans(1,1,qlen,*hou);
    		int  father;
    		if(deppre<dephou)//前面的节点是我的祖先 
    		{
    			if(!son[*pre][1])add(x,*pre),father=*pre;
    			else  add(x,*hou),father=*hou;
    		}
    		else
    		{
    			if(!son[*hou][0])add(x,*hou),father=*hou;
    			else  add(x,*pre),father=*pre;
    		}
    		if(father==*hou)
    		{
    			change2(1,1,qlen,x,dephou+1);
    			return  dephou+1;
    		}
    		else
    		{
    			change2(1,1,qlen,x,deppre+1);
    			return  deppre+1;
    		}
    	}
    }
    inline  void  del_dian(int  x)//x不能是根节点 
    {
    	int  f=fa[x],w=son[f][0]==x?0:1;
    	int  l=0,r=0;//修改的范围
    	if(w==0)l=x,r=f-1;//最小值 
    	else  l=f+1,r=x;//最大值 
    	change1(1,1,qlen,l,r,-1);//深度减少1 
    	fa[x]=0;
    	if(son[x][0])fa[son[x][0]]=f,son[f][w]=son[x][0];
    	else  fa[son[x][1]]=f,son[f][w]=son[x][1];
    }
    inline  int  del(int  x/*删除数字并且返回代价*/)
    {
    	fuck.erase(x);
    	if(x==root)
    	{
    		if(son[x][0])root=son[x][0],fa[root]=0,son[x][0]=0;
    		else  root=son[x][1],fa[root]=0,son[x][1]=0;
    		change1(1,1,qlen,1,qlen,-1);//全部深度减一 
    		return  1;
    	}
    	else
    	{
    		int  ans=findans(1,1,qlen,x);
    		del_dian(x);
    		return  ans;
    	}
    }
    int  q[N];int  id[N];
    inline  bool  cmp(int  x,int  y){return  q[x]<q[y];}
    int  main()
    {
    //	freopen("1.out","w",stdout);
    	scanf("%d",&n);
    	for(int  i=1;i<=n;i++)
    	{
    		int  type;
    		scanf("%d",&type);
    		if(type!=1)q[i]=-type;
    		else
    		{
    			int  x;scanf("%d",&x);
    			q[i]=x;
    			qlen++;id[qlen]=i;
    		}
    	}
    	sort(id+1,id+qlen+1,cmp);
    	for(int  i=1;i<=qlen;i++)q[id[i]]=i;
    	bt(1,qlen);
    	int  ans=0;
    	for(int  i=1;i<=n;i++)
    	{
    		ans=0;
    		if(q[i]>0)ans+=ins(q[i]);
    		else
    		{
    			if(q[i]==-2)
    			{
    				int  x=*fuck.begin();
    				ans+=del(x);
    				fuck.insert(x);
    				if(!root)root=x;
    				else  fa[root]=x,son[x][1]=root,root=x;
    				change1(1,1,qlen,1,qlen,1);
    				change2(1,1,qlen,root,1);
    			}
    			else  if(q[i]==-3)
    			{
    				int  x=*(--fuck.end());
    				ans+=del(x);
    				fuck.insert(x);
    				if(!root)root=x;
    				else  fa[root]=x,son[x][0]=root,root=x;
    				change1(1,1,qlen,1,qlen,1);
    				change2(1,1,qlen,root,1);
    			}
    			else  if(q[i]==-4)ans+=del(*fuck.begin());
    			else  ans+=del(*(--fuck.end()));
    		}
    		printf("%d
    ",ans);
    	}
    	return  0;
    }
    
  • 相关阅读:
    需求获取常见的方法是进行客户访谈,结合你的实践谈谈会遇到什么问题,你是怎么解决的?
    软件工程导论 习题五
    软件工程导论 习题四
    软件工程导论 习题三
    软件工程导论 习题二(1.2.3.5)
    软件工程导论 习题一
    面向对象分析方法和面向过程分析方法的区别
    几大开发模型区别与联系
    项目总结
    关于需求
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/13930978.html
Copyright © 2020-2023  润新知