洛谷3165 CQOI2014 排序机械臂 splay

题目链接

题意:
给你一个长度为 n n n的序列,现在有一种对序列排序的方法:假设当前最小的前 i i i个已经找到了,我们找到 i + 1 i+1 i+1 n n n里面最早出现的最小值,设其出现位置为 j j j,我们将区间 [ i + 1 , j ] [i+1,j] [i+1,j]翻转来排序。输出每次翻转前要找的最小值的位置。
n < = 1 e 5 n<=1e5 n<=1e5

题解:
首先先吐槽一下,最近可能我实在是太蠢了,这个题硬是写了一天半。网上题解看了不少,都没看懂,感觉好像他们建的平衡树的下标含义和我似乎都不一样。于是最后还是自己yy了一种做法。

这种区间翻转的题,我们还是类似文艺平衡树的方法去用splay维护,为了提取区间方便,我们还是在头尾各加一个节点。我这里splay维护的顺序就是序列的位置顺序,也就是最初中序遍历splay就能得到原序列,只不过我没有在splay的节点上记录数值。

我将每个位置的数值和下标排序,得到这个位置应该是第几次被翻转,记录到了 r k rk rk数组中。对于第 i i i次,我们找到翻转要提取区间,提取区间的前驱是 r k [ i − 1 ] rk[i-1] rk[i1],后继是 r k [ i ] rk[i] rk[i]的后继,将 r k [ i − 1 ] rk[i-1] rk[i1]转到根,再将那个后继转到根的右儿子,这样区间就被提取到后继的左子树了,就是经典的splay提取区间操作。然后对提取的区间打上翻转标记。每次输出的答案就是将 r k [ i ] rk[i] rk[i]转到根后左子树的大小,本来应该要加一,但是我们左端加了一个点,所以就不用加一了。splay维护的唯一量就是所在子树的size。

特别说一下区间翻转标记的pushdown问题。这里感觉对pushdown有了一些新的理解,但是不保证的对的。感觉网上始终没人说splay的区间标记什么时候应该pushdown,于是我们能见到各自版本的,有的只有在逐层向下查找时pushdown,除了逐层向下时一定要pushdown之外,有的只在splay里pushdown,有的只在rotate里面pushdown,有的既在splay里pushdown又在rotate里pushdown。我这里的写法是在逐层向下查找时pushdown和在splay里pushdown,顺序都是先根节点后子节点。我的理解是,只要需要用到左右儿子信息的时候,就要先pushdown,只要能满足这个,就可以了。而可以不在rotate里面写是因为splay里面已经先对要rotate操作的节点进行了一些条件判断,而在这些判断之前做完pushdown,就可以不在rotate里面pushdown了。

另外这是我第一次把splay写到一个结构体了,虽然这里不写到结构体里应该也没什么区别。

不知道是不是和网上很多题解不太一样的做法,希望能给大家提供一种新思路。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,a[100010],root,rk[100010];
struct node
{
	int sz,c[2],rev,f;
}tr[400010]; 
struct qwq
{
	int id,v;
}b[101000];
int cmp(qwq x,qwq y)
{
	if(x.v==y.v)
	return x.id<y.id;
	return x.v<y.v;
}
inline void pushup(int rt)
{
	tr[rt].sz=tr[tr[rt].c[0]].sz+tr[tr[rt].c[1]].sz+1;
}
inline void build(int rt,int l,int r)
{
	int mid=(l+r)>>1;
	if(rt)
	{
		if(mid<rt)
		tr[rt].c[0]=mid;
		else
		tr[rt].c[1]=mid;
	}
	tr[mid].f=rt;
	tr[mid].sz=1;
	if(l!=r)
	{
		if(l<mid)
		build(mid,l,mid-1);
		if(mid+1<=r)
		build(mid,mid+1,r);
	}
	pushup(mid);
}
inline void pushdown(int rt)
{
	if(tr[rt].rev)
	{
		if(tr[rt].c[0])
		tr[tr[rt].c[0]].rev^=1;
		if(tr[rt].c[1])
		tr[tr[rt].c[1]].rev^=1;
		swap(tr[rt].c[0],tr[rt].c[1]);
		tr[rt].rev=0;
	}
}
inline void rotate(int x)
{	
	int y=tr[x].f,z=tr[y].f,k=tr[y].c[1]==x,w=tr[x].c[!k];
	if(y!=root)
	tr[z].c[tr[z].c[1]==y]=x;
	tr[x].c[!k]=y;
	tr[y].c[k]=w;
	if(w)
	tr[w].f=y;
	tr[y].f=x;
	tr[x].f=z;	
	pushup(y);
	pushup(x);
}
inline void splay(int x,int rt)
{
	while(tr[x].f!=rt)
	{
		int y=tr[x].f,z=tr[y].f;	
		pushdown(z);
		pushdown(y);						
		pushdown(x);
		if(tr[y].f!=rt)
		{
			if(tr[z].c[0]==y ^ tr[y].c[0]==x)
			rotate(x);
			else
			rotate(y);
		}
		rotate(x);
		if(rt==0)
		root=x;
	}
}
inline int find()
{
	int x=root;
	pushdown(root);
	x=tr[root].c[1];
	while(x)
	{
		pushdown(x);
		if(tr[x].c[0])
		x=tr[x].c[0];
		else
		return x;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n+1;++i)
	scanf("%d",&a[i]);
	for(int i=2;i<=n+1;++i)
	{
		b[i-1].id=i;
		b[i-1].v=a[i];
	}
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=n;++i)
	rk[i+1]=b[i].id;
	rk[1]=1;
	rk[n+2]=n+2;
	a[1]=1e9;
	a[n+2]=1e9;
	a[0]=1e9;
	build(0,1,n+2);
	root=(1+n+2)>>1;
	for(int i=2;i<=n+1;++i)
	{
		splay(rk[i],0);
		int x=tr[tr[rk[i]].c[0]].sz;
		int yy=find();
		printf("%d ",x);
		splay(rk[i-1],0);
		splay(yy,rk[i-1]);
		int y=tr[root].c[1],z=tr[y].c[0];
		tr[z].rev^=1;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值