BZOJ3678 wangxz与OJ【Splay(缩点,split)/ 无旋Treap】

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/C20181220_xiang_m_y/article/details/102667571

题目描述:

维护一个初始有n个元素的序列(标记为1~n号元素),支持以下操作:
0 p a b (0<=p<=当前序列元素个数) (a<=b) 在p位置和p+1位置之间插入整数:a,a+1,a+2,…,b-1,b。若p为0,插在序列最前面;
1 a b (1<=a<=b<=当前序列元素个数) 删除a,a+1,a+2,…,b-1,b位置的元素;
2 p (1<=p<=当前序列元素个数) 查询p位置的元素。

n(1<=n<=20000),m(1<=m<=20000)

题目分析:

Splay做法:

这篇题解写的很好。

把一个连续区间看成一个点插入。
插入和删除都可以将区间split到根的右儿子的左儿子,然后直接修改。
如果查询的位置恰好在一个连续段的中间,就把这个段分成三个点(左边,中间一个点,右边)。
一个split要两次find,一次find会增加两个点,插入会增加一个点,所以空间要开到12w。

Code:

#include<bits/stdc++.h>
#define maxn 120005
using namespace std;
int n,m,root,pt,ch[maxn][2],fa[maxn],a[maxn],w[maxn],siz[maxn];
inline bool isc(int x){return ch[fa[x]][1]==x;}
inline void upd(int x){siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+w[x];}
int build(int l,int r){
	if(l>r) return 0;
	int mid=(l+r)>>1;
	ch[mid][0]=build(l,mid-1),fa[ch[mid][0]]=mid;
	ch[mid][1]=build(mid+1,r),fa[ch[mid][1]]=mid;
	w[mid]=1,upd(mid);
	return mid;
}
void rot(int x,int &rt){
	int y=fa[x],z=fa[y];bool c=isc(x);
	if(y==rt) rt=x; else ch[z][isc(y)]=x;
	fa[ch[y][c]=ch[x][!c]]=y,fa[ch[x][!c]=y]=x,fa[x]=z;//ignore fa[0]
	upd(y),upd(x);
}
void splay(int x,int &rt){
	for(;x!=rt;rot(x,rt))
		if(fa[x]!=rt) rot(isc(x)==isc(fa[x])?fa[x]:x,rt);
}
int find(int x,int k){
	if(k<=siz[ch[x][0]]) return find(ch[x][0],k);
	if(k>siz[ch[x][0]]+w[x]) return find(ch[x][1],k-siz[ch[x][0]]-w[x]);
	k-=siz[ch[x][0]];
	if(k>1) a[++pt]=a[x],w[pt]=k-1,fa[ch[pt][0]=ch[x][0]]=pt,fa[ch[x][0]=pt]=x,upd(pt);
	if(k<w[x]) a[++pt]=a[x]+k,w[pt]=w[x]-k,fa[ch[pt][1]=ch[x][1]]=pt,fa[ch[x][1]=pt]=x,upd(pt);
	a[x]=a[x]+k-1,w[x]=1; return x;
}
int split(int l,int r){
	int a=find(root,l),b=find(root,r+2);//find won't modify a -> root's siz
	splay(a,root),splay(b,ch[root][1]); return ch[ch[root][1]][0];
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n+1;i++) scanf("%d",&a[i]);
	root=build(1,n+2),pt=n+2;
	int op,x,l,r;
	while(m--){
		scanf("%d%d",&op,&x);
		if(op==0){
			scanf("%d%d",&l,&r),split(x+1,x);
			fa[ch[ch[root][1]][0]=++pt]=ch[root][1],a[pt]=l,siz[pt]=w[pt]=r-l+1,upd(ch[root][1]),upd(root);
		}
		else if(op==1){
			scanf("%d",&r),split(x,r);
			ch[ch[root][1]][0]=0,upd(ch[root][1]),upd(root);
		}
		else printf("%d\n",a[split(x,x)]);
	}
}

无旋Terap做法:

大致思路是一样的,不过用无旋Treap实现按siz来split比起Splay实在是太好写了。。。而且也非常的无脑。。
注意查询之后要merge回去。

#include<bits/stdc++.h>
#define maxn 60005
using namespace std;
int n,m,lc[maxn],rc[maxn],v[maxn],w[maxn],rnd[maxn],siz[maxn],tot,rt;
inline int Newnode(int x,int len){
	v[++tot]=x,siz[tot]=w[tot]=len,lc[tot]=rc[tot]=0,rnd[tot]=rand()<<15|rand();
	return tot;
}
inline void upd(int x){siz[x]=siz[lc[x]]+siz[rc[x]]+w[x];}
void merge(int &t,int a,int b){
	if(!a||!b) {t=a+b;return;}
	if(rnd[a]<rnd[b]) t=a,merge(rc[t],rc[a],b);
	else t=b,merge(lc[t],a,lc[b]);
	upd(t);
}
void split(int t,int &a,int &b,int k){
	if(!t) {a=b=0;return;}
	if(k<=siz[lc[t]]) b=t,split(lc[t],a,lc[b],k);
	else if(k>=siz[lc[t]]+w[t]) a=t,split(rc[t],rc[a],b,k-siz[lc[t]]-w[t]);
	else k-=siz[lc[t]],rc[Newnode(v[t]+k,w[t]-k)]=rc[t],rc[t]=0,w[t]=k,a=t,b=tot,upd(tot);
	upd(t);
}
int main()
{
	int op,x,l,r,a,b,c;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&x),merge(rt,rt,Newnode(x,1));
    while(m--){
        scanf("%d%d",&op,&x);
        if(op==0) scanf("%d%d",&l,&r),split(rt,a,b,x),merge(a,a,Newnode(l,r-l+1)),merge(rt,a,b);
        if(op==1) scanf("%d",&r),split(rt,a,b,r),split(a,a,c,x-1),merge(rt,a,b);
        if(op==2) split(rt,a,b,x),split(a,a,c,x-1),printf("%d\n",v[c]),merge(a,a,c),merge(rt,a,b);
    }
}

猜你喜欢

转载自blog.csdn.net/C20181220_xiang_m_y/article/details/102667571