洛谷P3285 [SCOI2014]方伯伯的OJ[Splay,STL map]

闲得没事,发现试炼场的平衡树只差一题就可以通过了,于是就来做了这一道题

此题既要维护编号,又要维护排名,还有_t_m_d1e8个用户,真想知道方伯伯的脑子是用什么做的

盯着题目看了半天,一脸懵逼,于是看题解,发现一个超有道理的做法:

  • 建一棵以排名为关键字的Splay,再开一个map,把编号映射为在Splay中的位置

乍一看好像好有道理,但再一想,1e8个用户,Splay玩个*啊

这时候就要用上一个高端优化。他有1e8个用户,但只有1e5个操作,那么我们可以把未被操作过的、连续的用户并在一起,等到有改动时在切成3块:l~x-1,x,x~r。这样一来,Splay中最多也只会有3e5个节点,可以接受

还有一个细节:我们怎么知道这个用户所在的块在Splay中的哪里呢?

我们可以把每一个块的右端点的位置存在map里,等到要查询一个节点的位置时,就可以mp.lower_bound(x)->second,得到位置。不过每次拆分或删除后,一定要改变或删除map中的数据

为了更清楚地理解,以下摘抄自Fire_Storm的题解:

以排名或编号建树都不能完美满足所有操作的要求,所以我们同时以排名和编号为序建立两棵平衡树 T1​ , T2​ 。

T1​ 以排名为序, T2​ 以编号为序, T2​ 中保存这个编号在 T1​ 中对应的节点编号。

操作一,在 T2​ 中找到编号,回到 T1​ 中算答案,然后直接更新 T2​ 即可。

其他操作类似。

另外此题最多有 10^8名用户,但只有 10^5个操作,那么我们可以把没有访问过的一段用户合成一个点,访问到其中时再分裂。

T2​ 的功能单一,用map就好了。

放长长的代码:

#include<bits/stdc++.h>
#define sz 330050
using namespace std;
map<int,int>mp;//编号在splay中的位置
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
int n,m,ans;
int ch[sz][2],fa[sz],L[sz],R[sz],size[sz];
int root,cnt;
inline void pushup(int x){size[x]=size[ls(x)]+size[rs(x)]+R[x]-L[x]+1;}
inline bool get(int x){return rs(fa[x])==x;}
inline void rotate(int x)
{
	int y=fa[x],z=fa[y],k=get(x),w=ch[x][!k];
	if (z) ch[z][get(y)]=x;ch[x][!k]=y;ch[y][k]=w;
	if (w) fa[w]=y;fa[y]=x;fa[x]=z;
	pushup(y);pushup(x);
}
inline void splay(int x,int to)
{
	while (fa[x]!=to)
	{
		int y=fa[x];
		if (fa[y]!=to) rotate(get(x)==get(y)?y:x);
		rotate(x);
	}
	if (!to) root=x;
}
int kth(int k)
{
	int x=root;
	while (233)
	{
		int S=size[ls(x)]+R[x]-L[x]+1;
		if (size[ls(x)]<k&&S>=k) return L[x]+k-size[ls(x)]-1;
		if (k<=S) x=ls(x);
		else k-=S,x=rs(x);
	}
}
inline void split(int x,int k)
{
	int l,r;
	mp[k]=x;
	if (L[x]==R[x]) return;
	if (L[x]==k)
	{
		r=++cnt;
		mp[R[x]]=r;
		L[r]=k+1;R[r]=R[x];R[x]=k;
		if (rs(x)) fa[rs(x)]=r;rs(r)=rs(x);rs(x)=r;fa[r]=x;
		pushup(r);pushup(x);
		return;
	}
	if (R[x]==k)
	{
		l=++cnt;
		mp[k-1]=l;
		L[l]=L[x];R[l]=k-1;L[x]=k;
		if (ls(x)) fa[ls(x)]=l;ls(l)=ls(x);ls(x)=l;fa[l]=x;
		pushup(l);pushup(x);
		return;
	}
	r=++cnt;l=++cnt;
	mp[k-1]=l;mp[R[x]]=r;
	L[l]=L[x];R[l]=k-1;L[r]=k+1;R[r]=R[x];L[x]=R[x]=k;
	if (rs(x)) fa[rs(x)]=r;rs(r)=rs(x);rs(x)=r;fa[r]=x;
	if (ls(x)) fa[ls(x)]=l;ls(l)=ls(x);ls(x)=l;fa[l]=x;
	pushup(l);pushup(r);pushup(x);
}
void del(int x)
{
	splay(x,0);
	if (!ls(x)){root=rs(x);fa[root]=0;return;}
	if (!rs(x)){root=ls(x);fa[root]=0;return;}
	int pre=ls(x),scc=rs(x);
	while (rs(pre)) pre=rs(pre);
	while (ls(scc)) scc=ls(scc);
	splay(pre,0);
	splay(scc,pre);
	ls(scc)=0;
	pushup(scc);pushup(pre);
}
void p_front(int k)
{
	int x=root;
	while (ls(x)) x=ls(x);
	ls(x)=k;size[k]=1;ls(k)=rs(k)=0;fa[k]=x;
	splay(k,0);
}
void p_back(int k)
{
	int x=root;
	while (rs(x)) x=rs(x);
	rs(x)=k;size[k]=1;ls(k)=rs(k)=0;fa[k]=x;
	splay(k,0);
}
inline int query(int x)
{
	splay(x,0);
	return size[x]-size[rs(x)];
}
void debug(int x)
{
	if (ls(x)) debug(ls(x));
	for (int i=L[x];i<=R[x];i++) printf("%d ",i);
	if (rs(x)) debug(rs(x));
}
inline int read()
{
	register int ret=0;
	register char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') ret=ret*10+ch-48,ch=getchar();
	return ret;
}
int main()
{
	n=read();m=read();
	root=++cnt;
	L[1]=1;R[1]=n;size[1]=n;
	mp[n]=1;
	int x,opt,i;
	for (i=1;i<=m;i++)
	{
		opt=read();
		if (opt==1)
		{
			int oid=read()-ans,nid=read()-ans;
			x=mp.lower_bound(oid)->second;
			split(x,oid);
			ans=query(x);
			L[x]=R[x]=nid;mp[nid]=x;
			mp.erase(oid);
			printf("%d\n",ans);
		}
		else if (opt==2)
		{
			int id=read()-ans;
			int x=mp.lower_bound(id)->second;
			split(x,id);
			ans=query(x);
			del(x);
			p_front(x);
			printf("%d\n",ans);
		}
		else if (opt==3)
		{
			int id=read()-ans;
			int x=mp.lower_bound(id)->second;
			split(x,id);
			ans=query(x);
			del(x);
			p_back(x);
			printf("%d\n",ans);
		}
		else if (opt==4)
		{
			int k=read()-ans;
			ans=kth(k);
			printf("%d\n",ans);
		}
	}
}

最后,说一下这题暴露出的我的漏洞:Splay不熟练。del操作中没有左(右)儿子时,我竟然在调整root后忘记把fa[root]赋为0,为此调了一个下午,身败名裂。。。

猜你喜欢

转载自blog.csdn.net/pb122401/article/details/81264059