luogu P3391 【模板】文艺平衡树(Splay)

背景:

发现 s p l a y splay splay学得像 s h i t shit shit使得,重新搞过,顺带写博客。


题目传送门:

https://www.luogu.org/problemnew/show/P3391

题意:

维护一个支持区间翻转的序列。

思路:

显然我们不能直接在 s p l a y splay splay中操作。
容易发现对一个区间进行两次翻转的效果是不变的。不妨用 l a z y lazy lazy来记录当前节点是否需要翻转。
因为 s p l a y splay splay的性质(左儿子<根<右儿子)满足了区间翻转就是交换字树内的左右儿子,那就可以让 l a z y lazy lazy下传。

考虑到这里我们还是不知道该如何旋转区间 [ l , r ] [l,r] [l,r]。你在想想 s p l a y splay splay还有那些性质。我们将 l − 1 l-1 l1这个点旋转作为整棵树的根,那么再将 r + 1 r+1 r+1旋转作为当前根的右儿子,那么要翻转的区间就是 r + 1 r+1 r+1的左儿子所管理的子树,直接打上 l a z y lazy lazy标志即可。
由于可能会出现 0 0 0这样的节点,所以整体偏移一位即可。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
	struct node{int d,fa,c,n,son[2],lazy;} tr[100010];
	int n,m,root=0,len=0;
void ups(int x)
{
	int lc=tr[x].son[0],rc=tr[x].son[1];
	tr[x].c=tr[lc].c+tr[rc].c+tr[x].n;
	if(tr[x].lazy)
	{
		tr[lc].lazy^=1,tr[rc].lazy^=1;
		tr[x].son[0]=rc,tr[x].son[1]=lc;
		tr[x].lazy=0;
	}
}
void add(int d,int fa)
{
	tr[++len]=(node){d,fa,1,1,0,0,0};
}
void rot(int x,int w)//左旋:0,右旋:1
{
	int y=tr[x].fa,yy=tr[y].fa;
	tr[y].son[1^w]=tr[x].son[w];
	if(tr[x].son[w]) tr[tr[x].son[w]].fa=y;
	tr[yy].son[tr[yy].son[0]==y?0:1]=x;
	
	tr[x].fa=yy,tr[x].son[w]=y,tr[y].fa=x;
	
	ups(y),ups(x);
}
void splay(int x,int rt)
{
	while(tr[x].fa!=rt)
	{
		int y=tr[x].fa,yy=tr[y].fa;
		if(yy==rt)
		{
			rot(x,tr[y].son[0]==x?1:0);
			continue;
		}
		if(tr[yy].son[0]==y)
		{
			if(tr[y].son[0]==x) rot(y,1),rot(x,1); else rot(x,0),rot(x,1);
			continue;
		}
		if(tr[y].son[1]==x) rot(y,0),rot(x,0); else rot(x,1),rot(x,0); 
	}
	if(!rt) root=x;
}
int findip(int x,int d)
{
	if(d==tr[x].d) return x;
	if(d<tr[x].d)
		return tr[x].son[0]?findip(tr[x].son[0],d):x;
	else
		return tr[x].son[1]?findip(tr[x].son[1],d):x;
}
void ins(int d)
{
	if(!root)
	{
		add(d,0);
		root=1;
		return;
	}
	
	int u=findip(root,d);
	if(tr[u].d==d)
	{
		tr[u].n++;
		ups(u);
		splay(u,0);
		return;
	}
	
	add(d,u);
	tr[u].son[d<tr[u].d?0:1]=len;
	ups(u);
	splay(len,0);
}
int findnum(int x,int d)
{
	ups(x);
	int lc=tr[x].son[0],rc=tr[x].son[1];
	if(d<=tr[lc].c) return findnum(lc,d);
	else if(d>tr[lc].c+tr[x].n) return findnum(rc,d-tr[lc].c-tr[x].n);
	else return tr[x].d;
}
void work(int x,int y)
{
	int t1=findnum(root,x)+1,t2=findnum(root,y+2)+1;
	splay(t1,0);
	splay(t2,t1);
	tr[tr[t2].son[0]].lazy^=1;
}
void dfs(int x)
{
	ups(x);
	if(tr[x].son[0]) dfs(tr[x].son[0]);
	if(x>=2&&x<=n+1) printf("%d ",x-1);
	if(tr[x].son[1]) dfs(tr[x].son[1]);
}
int main()
{
	int x,y;
	scanf("%d %d",&n,&m);
	for(int i=0;i<=n+2;i++)
		ins(i);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		work(x,y);
	}
	dfs(root);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值