• 【刷题】BZOJ 4825 [Hnoi2017]单旋


    Description

    H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能。有一天,邪恶的“卡”带着他的邪恶的“常数”来企图毁灭 H 国。“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快。“卡”称“单旋 splay”为“spaly”。虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,spaly 马上成为他的信仰。 而 H 国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由 m 个操作构成,他知道这样的数据肯定打垮 spaly,但是国王还有很多很多其他的事情要做,所以统计每个操作所需要的实际代价的任务就交给你啦。

    数据中的操作分为五种:

    1. 插入操作:向当前非空 spaly 中插入一个关键码为 key 的新孤立节点。插入方法为,先让 key 和根比较,如果key 比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,key 比当前子树根 x 小,而 x 的左子树为空,那就让 key 成为 x 的左孩子; 或者 key 比当前子树根 x 大,而 x 的右子树为空,那就让 key 成为x 的右孩子。该操作的代价为:插入后,key 的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树。(各节点关键码互不相等。对于“深度”的解释见末尾对 spaly 的描述)。

    2. 单旋最小值:将 spaly 中关键码最小的元素 xmin 单旋到根。操作代价为:单旋前 xmin 的深度。(对于单旋操作的解释见末尾对 spaly 的描述)。

    3. 单旋最大值:将 spaly 中关键码最大的元素 xmax 单旋到根。操作代价为:单旋前 xmax 的深度。

    4. 单旋删除最小值:先执行 2 号操作,然后把根删除。由于 2 号操作之后,根没有左子树,所以直接切断根和右子树的联系即可(具体见样例解释)。 操作代价同 2 号操 作。

    5. 单旋删除最大值:先执行 3 号操作,然后把根删除。 操作代价同 3 号操作。

    对于不是 H 国的人,你可能需要了解一些 spaly 的知识,才能完成国王的任务:

    a. spaly 是一棵二叉树,满足对于任意一个节点 x,它如果有左孩子 lx,那么 lx 的关键码小于 x 的关键码。如果有右孩子 rx,那么 rx 的关键码大于 x 的关键码。

    b. 一个节点在 spaly 的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。

    c. 单旋操作是对于一棵树上的节点 x 来说的。一开始,设 f 为 x 在树上的父亲。如果 x 为 f 的左孩子,那么执行 zig(x) 操作(如上图中,左边的树经过 zig(x) 变为了右边的树),否则执行 zag(x) 操作(在上图中,将右边的树经过 zag(f) 就变成了左边的树)。每当执 行一次 zig(x) 或者 zag(x),x 的深度减小 1,如此反复,直到 x 为根。总之,单旋 x 就是通过反复执行 zig 和 zag 将 x 变为根。

    Input

    第一行单独一个正整数 m。

    接下来 m 行,每行描述一个操作:首先是一个操作编号 c∈[1,5],即问题描述中给出的五种操作中的编号,若 c = 1,则再输入一个非负整数 key,表示新插入节点的关键码。

    1≤m≤105,1≤key≤109

    所有出现的关键码互不相同。任何一个非插入操作,一定保证树非空。在未执行任何操作之前,树为空

    Output

    输出共 m 行,每行一个整数,第 i 行对应第 i 个输入的操作的代价。

    Sample Input

    5
    1 2
    1 1
    1 3
    4
    5

    Sample Output

    1
    2
    2
    2
    2

    Solution

    题目看了很久
    操作一个一个看
    第一个:对于一个splay,如果我们有一个它的中序遍历的数组,那么新插一个数,这个数要么是在数组的最前面或最后面(这种情况特判掉,不在后面的考虑范围),要么就一定被夹在数组中相邻两个数中间没,称前面那个数为前驱,后面的为后继,所以新插的这个数,要么是前驱的右儿子,要么是后继的左儿子;然后就一个性质,前驱的右儿子和后继的左儿子恰好有且仅有一个位置是空的,为什么?反证,考虑如果两个位置都是空的,但前驱和后继肯定有一个共同的祖先,而这个祖先根据中序遍历,在中序遍历的数组中会夹在前驱和后继中间,这样前驱后继不相邻,就不是新插数的前驱和后继了。然后有着这个性质,就只要快速找到它的前驱和后继,判断插在哪个位置就行了。这个中序遍历的数组可以用set维护,而找位置,upper_bound就可以了
    第二/三个:手动画几个树,旋一下,会发现,由于修改的只是两个最值,最左边的和最右边的,最后的形状是差不多的,对于Mn,就是它的右儿子代替自己的位置,它变成了根,对于Mx,就是它的左儿子代替自己的位置,它变成了根,直接 (O(1)) 修改就行了
    第四/五个:既然已经知道怎么旋到根了,那到根之后就直接删掉就好了
    操作搞定了,然后想怎么维护答案
    可以用一个LCT直接维护,然后一个点的深度就是它与根access后,根的size
    所以这题两个东西
    一个LCT,维护每个点的深度
    一个Spaly,题目中说的东西,但不要暴力维护,按照上面的方法修改,只要记录父亲,左右儿子,中序遍历的序列,顺便再离散化一下就行了

    #include<bits/stdc++.h>
    #define ui unsigned int
    #define ll long long
    #define db double
    #define ld long double
    #define ull unsigned long long
    const int MAXN=100000+10;
    int m;
    #define lc(x) ch[(x)][0]
    #define rc(x) ch[(x)][1]
    struct LCT{
    	int ch[MAXN][2],fa[MAXN],rev[MAXN],size[MAXN],stack[MAXN],cnt;
    	inline bool nroot(int x)
    	{
    		return lc(fa[x])==x||rc(fa[x])==x;
    	}
    	inline void reverse(int x)
    	{
    		std::swap(lc(x),rc(x));
    		rev[x]^=1;
    	}
    	inline void pushup(int x)
    	{
    		size[x]=size[lc(x)]+size[rc(x)]+1;
    	}
    	inline void pushdown(int x)
    	{
    		if(rev[x])
    		{
    			if(lc(x))reverse(lc(x));
    			if(rc(x))reverse(rc(x));
    			rev[x]=0;
    		}
    	}
    	inline void rotate(int x)
    	{
    		int f=fa[x],p=fa[f],c=(rc(f)==x);
    		if(nroot(f))ch[p][rc(p)==f]=x;
    		fa[ch[f][c]=ch[x][c^1]]=f;
    		fa[ch[x][c^1]=f]=x;
    		fa[x]=p;
    		pushup(f);
    		pushup(x);
    	}
    	inline void splay(int x)
    	{
    		cnt=0;
    		stack[++cnt]=x;
    		for(register int i=x;nroot(i);i=fa[i])stack[++cnt]=fa[i];
    		while(cnt)pushdown(stack[cnt--]);
    		for(register int y=fa[x];nroot(x);rotate(x),y=fa[x])
    			if(nroot(y))rotate((lc(y)==x)==(lc(fa[y])==y)?y:x);
    		pushup(x);
    	}
    	inline void access(int x)
    	{
    		for(register int y=0;x;x=fa[y=x])splay(x),rc(x)=y,pushup(x);
    	}
    	inline void makeroot(int x)
    	{
    		access(x);splay(x);reverse(x);
    	}
    	inline void split(int x,int y)
    	{
    		makeroot(x);access(y);splay(y);
    	}
    	inline void link(int x,int y)
    	{
    		makeroot(x);fa[x]=y;
    	}
    	inline void cut(int x,int y)
    	{
    		split(x,y);fa[x]=lc(y)=0;pushup(y);
    	}
    };
    LCT T;
    #undef lc
    #undef rc
    template<typename T> inline void read(T &x)
    {
    	T data=0,w=1;
    	char ch=0;
    	while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    	if(ch=='-')w=-1,ch=getchar();
    	while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
    	x=data*w;
    }
    template<typename T> inline void write(T x,char c='')
    {
    	if(x<0)putchar('-'),x=-x;
    	if(x>9)write(x/10);
    	putchar(x%10+'0');
    	if(c!='')putchar(c);
    }
    struct Spaly{
    	int root,fa[MAXN],ls[MAXN],rs[MAXN],size;
    	std::set<int> S;
    	std::map<int,int> M;
    	inline void init()
    	{
    		root=0;
    		memset(fa,0,sizeof(fa));
    		memset(ls,0,sizeof(ls));
    		memset(rs,0,sizeof(rs));
    	}
    	inline void Insert(int x)
    	{
    		M[x]=++size;
    		if(!root)
    		{
    			root=M[x],write(1,'
    '),S.insert(x);
    			return ;
    		}
    		std::set<int>::iterator it=S.upper_bound(x);
    		if(it==S.end()||ls[M[*it]])--it,T.link(M[*it],M[x]),fa[M[x]]=M[*it],rs[M[*it]]=M[x];
    		else T.link(M[*it],M[x]),fa[M[x]]=M[*it],ls[M[*it]]=M[x];
    		S.insert(x);
    		T.split(M[x],root);
    		write(T.size[root],'
    ');
    	}
    	inline void Mnrotate()
    	{
    		int x=M[*S.begin()];	
    		if(x==root)
    		{
    			write(1,'
    ');
    			return ;
    		}
    		T.split(x,root);
    		write(T.size[root],'
    ');
    		T.cut(x,fa[x]);
    		if(rs[x])T.cut(x,rs[x]),T.link(rs[x],fa[x]);
    		T.link(root,x);
    		fa[rs[x]]=fa[x];ls[fa[x]]=rs[x];fa[root]=x;rs[x]=root;fa[x]=0;
    		root=x;
    	}
    	inline void Mxrotate()
    	{
    		int x=M[*--S.end()];
    		if(x==root)
    		{
    			write(1,'
    ');
    			return ;
    		}
    		T.split(x,root);
    		write(T.size[root],'
    ');
    		T.cut(x,fa[x]);
    		if(ls[x])T.cut(x,ls[x]),T.link(ls[x],fa[x]);
    		T.link(root,x);
    		fa[ls[x]]=fa[x];rs[fa[x]]=ls[x];fa[root]=x;ls[x]=root;fa[x]=0;
    		root=x;
    	}
    	inline void Mndelete()
    	{
    		int x=M[*S.begin()];
    		if(x==root)
    		{
    			if(rs[x])T.cut(x,rs[x]);
    			fa[rs[x]]=0;root=rs[x];
    			S.erase(S.begin());
    			write(1,'
    ');
    			return ;
    		}
    		T.split(x,root);
    		write(T.size[root],'
    ');
    		T.cut(x,fa[x]);
    		if(rs[x])T.cut(x,rs[x]),T.link(rs[x],fa[x]);
    		fa[rs[x]]=fa[x];ls[fa[x]]=rs[x];
    		S.erase(S.begin());
    	}
    	inline void Mxdelete()
    	{
    		int x=M[*--S.end()];
    		if(x==root)
    		{
    			if(ls[x])T.cut(x,ls[x]);
    			fa[ls[x]]=0;root=ls[x];
    			S.erase(--S.end());
    			write(1,'
    ');
    			return ;
    		}
    		T.split(x,root);
    		write(T.size[root],'
    ');
    		T.cut(x,fa[x]);
    		if(ls[x])T.cut(x,ls[x]),T.link(ls[x],fa[x]);
    		fa[ls[x]]=fa[x];rs[fa[x]]=ls[x];
    		S.erase(--S.end());
    	}
    };
    Spaly ST;
    template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
    template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
    template<typename T> inline T min(T x,T y){return x<y?x:y;}
    template<typename T> inline T max(T x,T y){return x>y?x:y;}
    int main()
    {
    	read(m);
    	ST.init();
    	int opt,x;
    	while(m--)
    	{
    		read(opt);
    		if(opt==1)read(x),ST.Insert(x);
    		if(opt==2)ST.Mnrotate();
    		if(opt==3)ST.Mxrotate();
    		if(opt==4)ST.Mndelete();
    		if(opt==5)ST.Mxdelete();
    	}
    	return 0;
    }
    
  • 相关阅读:
    Building Java Projects with Gradle
    Vert.x简介
    Spring及Spring Boot 国内快速开发框架
    dip vs di vs ioc
    Tools (StExBar vs Cmder)which can switch to command line window on context menu in windows OS
    SSO的定义、原理、组件及应用
    ModSecurity is an open source, cross-platform web application firewall (WAF) module.
    TDD中测试替身学习总结
    Spring事务银行转账示例
    台式机(华硕主板)前面板音频接口(耳机和麦克风)均无声的解决办法
  • 原文地址:https://www.cnblogs.com/hongyj/p/8735210.html
Copyright © 2020-2023  润新知