传送门
splay模板题(笑
对于基础算法这个熟练程度怎么去省选啊= =
题目中所给的编号即为平衡树中的点权。
开一个数组记录一下权值为i的在树中的变化是多少,每次询问的时候直接找。
对于Top操作:
如果树里只有一个点,什么都不用干;
否则,
首先把这个点删除,
然后找到树中的第一个点,
将这个点转到根,
将删除的那个点插到根的右儿子;
对于Bottom操作:
如果树里只有一个点,什么都不用干;
否则,
首先把这个点删除,
然后找到树中的第n-1个点,
(注意删除了之后树里只有n-1个点,不能寻找第n个点!)
将这个点转到根,
将删除的那个点插到根的右儿子;
对于Insert操作:
0的话什么都不用干;
如果是1或-1的话,
首先要判断这个点是否已经在树的最后一个和第一个,是的话什么都不用干,
否则,
将其中的一个转到根,另一个转到根的一个儿子,
交换这两个点,
注意这里会有一大堆鬼畜的父子关系变化(具体看代码)
(hxy神犇只find了之后交换了两个点的权值,Orz这种优越的姿势)
对于Ask操作:
将查询的这个点转到根,
返回根的左儿子的大小即为答案
对于Query操作:
做一个find就好啦!
具体见代码,有很详细的注释。
#include
#include
#include
using namespace std;
const int max_n=800005;
const int max_N=max_n*2;
int n,m,s,t;
char opt[10];
int a[max_n],k[max_n],loc[max_n];
int ch[max_N][2],f[max_N],size[max_N],key[max_N];
int root,sz;
inline void clear(int x){
ch[x][0]=ch[x][1]=f[x]=size[x]=key[x]=0;
}
inline int get(int x){
return ch[f[x]][1]==x;
}
inline void update(int x){
if (x){
size[x]=1;
if (ch[x][0]) size[x]+=size[ch[x][0]];
if (ch[x][1]) size[x]+=size[ch[x][1]];
}
}
inline void rotate(int x){
int old=f[x],oldf=f[old],which=get(x);
ch[old][which]=ch[x][which^1];
f[ch[old][which]]=old;
ch[x][which^1]=old;
f[old]=x;
f[x]=oldf;
if (oldf) ch[oldf][ch[oldf][1]==old]=x;
update(old);
update(x);
}
inline void splay(int x,int tar){
for (int fa;(fa=f[x])!=tar;rotate(x))
if (f[fa]!=tar)
rotate((get(x)==get(fa))?fa:x);
if (!tar) root=x;
}
inline int build(int l,int r,int fa){
if (l>r) return 0;
int mid=(l+r)>>1;
int now=++sz;
//每个点的权值为每个点的编号,loc记录每个编号在树中的位置
key[now]=k[mid]; loc[k[mid]]=now; f[now]=fa;
int lch=build(l,mid-1,now);
int rch=build(mid+1,r,now);
ch[now][0]=lch; ch[now][1]=rch;
update(now);
return now;
}
inline int find(int x){
int now=root;
while (1){
if (ch[now][0]&&x<=size[ch[now][0]])
now=ch[now][0];
else{
int temp=1;
if (ch[now][0]) temp+=size[ch[now][0]];
if (x==temp) return now;
x-=temp;
now=ch[now][1];
}
}
}
inline int pre(){
int now=ch[root][0];
while (ch[now][1]) now=ch[now][1];
return now;
}
inline int next(){
int now=ch[root][1];
while (ch[now][0]) now=ch[now][0];
return now;
}
inline void del(int x){
splay(x,0);
if (!ch[root][0]&&!ch[root][1]){
clear(root);
root=0;
return;
}
if (!ch[root][0]){
int oldroot=root; root=ch[oldroot][1]; f[root]=0; clear(oldroot); return;
}
if (!ch[root][1]){
int oldroot=root; root=ch[oldroot][0]; f[root]=0; clear(oldroot); return;
}
int leftbig=pre(),oldroot=root;
splay(leftbig,0);
ch[root][1]=ch[oldroot][1];
f[ch[oldroot][1]]=root;
clear(oldroot);
update(root);
}
inline void Swap(int x,int y,int opt){
//这里x表示的是要换到根的东西,y表示的是根
//交换两个点的size
swap(size[x],size[y]);
//lchx和rchx表示的是换到根的东西的两个儿子
int lchx=ch[x][0]; int rchx=ch[x][1];
//lchy和rchy表示的是根的两个儿子
int lchy=ch[y][0]; int rchy=ch[y][1];
//先将两个东西的两个儿子交换一下
ch[x][0]=lchy; ch[x][1]=rchy;
ch[y][0]=lchx; ch[y][1]=rchx;
//现在的根x的opt指向的那个儿子改为原来的根y
ch[x][opt]=y;
//原来的根y的父亲改为现在的根x,现在的根x的父亲为0
f[y]=x; f[x]=0;
//原来的根的另一个儿子指向现在的根
if (opt==0) f[rchy]=x;
else f[lchy]=x;
//原来的换成根的那个东西的两个儿子的父亲改成根
f[lchx]=f[rchx]=y;
root=x;
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%d",&k[i]);
//k表示每一个点的编号,在树中的权值
root=build(1,n,0);
for (int i=1;i<=m;++i){
scanf("%s",opt);
scanf("%d",&s);
switch(opt[0]){
case 'T':{
if (size[root]==1) continue;
//num为权值为s的点在树中的位置
int num=loc[s];
//删除这个点
del(num);
//find找树中位置第几个的点的编号,aa为第一个点的编号
int aa=find(1);
//将第一个点转到根
splay(aa,0);
//将当前点插到根的左子树
ch[root][0]=++sz;
f[sz]=root; size[sz]=1; key[sz]=s; loc[s]=sz;
update(root);
break;
}
case 'B':{
if (size[root]==1) continue;
int num=loc[s];
del(num);
//因为删去了一个,所以应该为n-1个
int aa=find(n-1);
//将第n-1个点转到根
splay(aa,0);
//将当前点插到根的右子树
ch[root][1]=++sz;
f[sz]=root; size[sz]=1; key[sz]=s; loc[s]=sz;
update(root);
break;
}
case 'I':{
if (size[root]==1) continue;
scanf("%d",&t);
if (!t) continue;
if (t==-1){
int num=loc[s];
//将当前点转到根
splay(num,0);
//如果没有左儿子,即它为序列的第一个,什么都不用干
if (!ch[root][0]) continue;
//将当前点的前驱转到根的左儿子
splay(pre(),num);
//交换根和根的左儿子
Swap(ch[root][0],root,0);
}
else{
int num=loc[s];
//将当前点转到根
splay(num,0);
//如果没有右儿子,即它为序列的最后一个,什么都不干
if (!ch[root][1]) continue;
//将当前点的后继转到根的右儿子
splay(next(),num);
//交换根和根的右儿子
Swap(ch[root][1],root,1);
}
break;
}
case 'A':{
int num=loc[s];
//将当前点转到根
splay(num,0);
//根的左儿子的大小即为前面有多少个
printf("%d\n",size[ch[root][0]]);
break;
}
case 'Q':{
//aa为第s本书的编号
int aa=find(s);
//输出这个编号对应的权值
printf("%d\n",key[aa]);
break;
}
}
}
}
这道题给我最重要的经验是父子关系的改变:
改变某两个点,有影响的点要向上向下都扩展一层,即离他们远一层的父亲和儿子;
这两个点的父子关系也要发生变化;
如果与根有牵扯,最后要确定根。