闲得没事,发现试炼场的平衡树只差一题就可以通过了,于是就来做了这一道题
此题既要维护编号,又要维护排名,还有1e8个用户,真想知道方伯伯的脑子是用什么做的
盯着题目看了半天,一脸懵逼,于是看题解,发现一个超有道理的做法:
- 建一棵以排名为关键字的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,为此调了一个下午,身败名裂。。。