1. 程式人生 > >洛谷P3285 [SCOI2014]方伯伯的OJ[Splay,STL map]

洛谷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,為此調了一個下午,身敗名裂。。。