数据结构自由交配(更新中)

三道数据结构板子题。

P2596 书架

点击查看 题意:维护一个数据结构,支持$5$种操作:
  • 将编号为\(x\)的数置顶
  • 将编号为\(x\)的数置底
  • 将编号为\(x\)的数提前/滞后\(1\)
  • 查询\(x\)排名
  • 查询排名为\(x\)的数。

splay好题。

对于操作一,我们将\(x\)旋转到根,然后把它的左子树与后继合并;
对于操作二,我们将\(x\)旋转到根,然后把它的右子树与前驱合并;
对于操作三,我们将\(x\)和它的前驱/后继交换位置和信息;
对于操作四,我们将\(x\)旋转到根,然后输出\(siz-1\)
对于操作五,我们直接在树上查找即可。

#include<bits/stdc++.h>
using namespace std;
const int N=8e5+10;
int n,m,sz,rt=1;
int siz[N],pos[N],fa[N],c[N][2],v[N];
char ch[7];
inline int read()
{
	int s=0,x=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
	while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*x;
}
void pushup(int x)
{
	siz[x]=siz[c[x][0]]+siz[c[x][1]]+1;
	pos[v[c[x][0]]]=c[x][0],pos[v[c[x][1]]]=c[x][1];
}
void rotate(int &rt,int x)
{
	int y=fa[x],z=fa[y],p,q=p;
	p=(c[y][0]!=x);q=p^1;
	if(y==rt)rt=x;
	else
	{
		if(c[z][0]==y)c[z][0]=x;
		else c[z][1]=x;
	}
	fa[x]=z;fa[y]=x;fa[c[x][q]]=y;
	c[y][p]=c[x][q];c[x][q]=y;
	pushup(y);pushup(x);
}
void splay(int &rt,int x)
{
	int y,z;
	while(x!=rt)
	{
		y=fa[x],z=fa[y];
		if(y!=rt)
		{
			if((c[z][0]==y)^(c[y][0]==x))rotate(rt,x);
			else rotate(rt,y);
		}
		rotate(rt,x);
	}
	pos[v[x]]=x;
}
void update(int x)
{
	v[++sz]=x;siz[sz]=1;pos[x]=sz;
	c[sz][0]=c[sz][1]=0;
	if(sz>1)
	{
		c[sz-1][1]=sz;fa[sz]=sz-1;
		splay(rt,sz);
	}
}
int find(int x,int k)
{
	int y=c[x][0];
	if(siz[y]+1==k)return x;
	else if(siz[y]+1<k)return find(c[x][1],k-siz[y]-1);
	else return find(y,k);
}
void top(int x)
{
	x=pos[x];
	splay(rt,x);
	if(!c[x][0])return ;
	if(!c[x][1])c[x][1]=c[x][0],c[x][0]=0;
	else
	{
		int y=find(rt,siz[c[x][0]]+2);
		fa[c[rt][0]]=y;
		c[y][0]=c[rt][0];
		c[rt][0]=0;
		splay(rt,y);
	}
}
void insert(int s,int x)
{
	if(!s)return ;int ps=pos[x];
	splay(rt,ps);
	int y=find(rt,s==1?siz[c[ps][0]]+2:siz[c[ps][0]]),z=v[y];
	swap(pos[x],pos[z]);swap(v[ps],v[y]);
}
void bottom(int x)
{
	x=pos[x];
	splay(rt,x);
	if(!c[x][1])return ;
	if(!c[x][0])c[x][0]=c[x][1],c[x][1]=0;
	else
	{
		int y=find(rt,siz[c[x][0]]);
		fa[c[rt][1]]=y;
		c[y][1]=c[rt][1];
		c[rt][1]=0;
		splay(rt,y);
	}
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;++i)update(read());
	while(m--)
	{
		scanf("%s",ch);
		switch(ch[0])
		{
			case 'T':{
				top(read());
				break;
			}
			case 'B':{
				bottom(read());
				break;
			}
			case 'I':{
				insert(read(),read());
				break;
			}
			case 'A':{
				int x=read();x=pos[x];
				splay(rt,x);
				printf("%d\n",siz[c[x][0]]);
				break;
			}
			case 'Q':{
				printf("%d\n",v[find(rt,read())]);
				break;
			}
		}
	}
	return 0;
}

P4402 robotic sort 机械排序

点击查看 题意:给定一个序列,对于第$i$次操作,找到第$i$小的数,输出现在的位置并将区间$[i,p_i]$翻转,若$p_i$已经在正确的位置,则将区间$[p_i,p_i]$翻转。

上一个是splay,所以这次就用treap。

我们用treap维护剩下的区间最小值。对于每次操作,我们找到最小值,输出它,然后将左右儿子合并,打上翻转标记即可。
然后就没了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+1;
int tot,n,m,stk[N],tp,rt;
int siz[N],w[N],son[N][2];
ll val[N];
bool lazy[N];
inline void update(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;}
void pushdown(int x)
{
	swap(son[x][1],son[x][0]);
	if(son[x][1])lazy[son[x][1]]^=1;
	if(son[x][0])lazy[son[x][0]]^=1;
	lazy[x]=0;
}
void build(int x)
{
	while(tp&&val[x]<val[stk[tp]])son[x][0]=stk[tp--],update(son[x][0]);
	if(tp)son[stk[tp]][1]=x;
	stk[++tp]=x;
}
int merge(int x,int y)
{
	if(!x||!y)return x|y;
	if(val[x]<=val[y])
	{
		if(lazy[x])pushdown(x);
		son[x][1]=merge(son[x][1],y);
		update(x);
		return x;
	}
	if(lazy[y])pushdown(y);
	son[y][0]=merge(x,son[y][0]);
	update(y);
	return y;
}
void split(int x)
{
	int l=son[x][0],r=son[x][1];
	son[x][0]=son[x][1]=0;
	lazy[l]^=1;
	rt=merge(l,r);
}
int l,r,a,b,c;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		int x;scanf("%d",&x);
		val[i]=1ll*x*n+i;siz[i]=1;
		build(i);
	}
	while(tp)update(stk[tp--]);
	rt=stk[1];
	for(int i=1;i<=n;++i)
	{
		if(lazy[rt])pushdown(rt);
		printf("%d ",siz[son[rt][0]]+i);
		split(rt);
	}
	return 0;
}

at_abc324_g Generate Arrays

点击查看

题意:给定一个序列,初始为零号,有两种操作\((t,s,x)\)

  • \(t=1\)时,将\(s\)号序列下标大于\(x\)的数删除,删除的数构成新序列;
  • \(t=2\)时,将\(s\)号序列大于\(x\)的数删除,删除的数构成新序列。

主席树板子题。

可以发现,操作1就是将序列按下标分为一段前缀和一段后缀,并将后缀加入到新序列中;操作2就是将序列按值域分为前缀和后缀,并将后缀加入到新序列中。

我们用四个数组l,r,minn,maxn表示区间l到r中值域为minn到maxn的数。
知道上面的信息后可以直接套主席树板子求出新序列,那么现在的主要问题就变为了如何处理修改操作。
对于操作2,直接将原序列复制一份到新序列,并将原序列的maxn改为x,新序列的minn改为x+1即可。
对于操作1,我们可以发现,其实就是求序列中大于等于minn小于等于maxn的第k小,经典主席树问题。主席树套二分可以解决。
复杂度\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define l(p) tr[p].l
#define r(p) tr[p].r
#define sum(p) tr[p].sum
#define L(p) t[p].L
#define R(p) t[p].R
#define mx(p) t[p].maxn
#define mi(p) t[p].minn
using namespace std;
const int N=2e5+10;
struct node{
	int l,r,sum;
}tr[N<<3];
struct seg{
	int L,R,maxn,minn;
}t[N];
int n,q,rt[N],cnt;
int build(int p,int l,int r,int x)
{
	int st=++cnt;
	tr[st]=tr[p];
	if(l==r)
	{
		sum(st)++;
		return st;
	}
	int mid=l+r>>1;
	if(x<=mid)l(st)=build(l(st),l,mid,x);
	else r(st)=build(r(st),mid+1,r,x);
	sum(st)=sum(l(st))+sum(r(st));
	return st;
}
int query(int x,int y,int l,int r,int mik,int mak)
{
	if(mak<mik)return 0;
	if(l>=mik&&r<=mak)return sum(x)-sum(y);
	int mid=l+r>>1,ans=0;
	if(mik<=mid)ans+=query(l(x),l(y),l,mid,mik,mak);
	if(mak>mid)ans+=query(r(x),r(y),mid+1,r,mik,mak);
	return ans;
}
inline int read()
{
	int s=0,x=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
	while(isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*x;
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)rt[i]=build(rt[i-1],1,n,read());
	q=read();
	for(int i=1;i<=q;++i)
	{
		int opt=read(),o=read(),p=read();
		t[i]=t[o];
		if(opt==1)
		{
			int L=L(o)-1,R=R(o);
			while(L<R)
			{
				int mid=L+R>>1;
				if(query(rt[mid],rt[l(o)-1],1,n,mi(o),mx(o))<=p)L=mid+1;
				else R=mid;
			}
			L(i)=L+1;
			R(o)=L;
		}
		else
		{
			mi(i)=max(mi(i),p+1);
			mx(o)=min(mx(o),p);
		}
		printf("%d\n",max(0,query(rt[r(i)],rt[l(i)-1],1,n,mi(i),mx(i))));
	}
	return 0;
}

posted @ 2025-04-12 22:51  leizepromax  阅读(32)  评论(0)    收藏  举报