P2596 [ZJOI2006]书架 题解

· · 个人记录

这是一种普通线段树的做法

可以发现我们要维护每个点的位置以及编号,如果暴力处理每次操作的编号必定会TLE,但是可以发现这道题每次操作只涉及一个或者两个点,于是把可以想象这是一个长度为3*n的书架:

时间复杂度只有 O(mlog(3*n)) ,比平衡树小了不少,实测5*n最慢也只有88ms(不开O2)

代码也短了一些(没压行):

#include<stdio.h>
#include<algorithm>
#define lk k<<1
#define rk k<<1|1
#define MAXN 400005//理论3*n就可以了,但是我死活过不了RE#8,怒开5*n
using namespace std;
int ls,rs;
class node{
public:
    int l,r,w,siz;
    //w:这个位置上书的编号,siz:有多少书
}t[MAXN<<2];
int n,m,id[MAXN],a[MAXN],tx,ty,tmp,an,t2;
//      id:每个节点的位置
char op[10];
void update(int k){
    t[k].siz=t[lk].siz+t[rk].siz;//统计区间有多少非空书架
}
void build(int k,int l,int r){
    t[k].l=l,t[k].r=r;
    if(l==r){
        t[k].siz=0;
        if(l>2*n and l<=3*n){//最开始把书堆放在中间,保证top和bottom操作有充足的空间
            t[k].w=a[l-2*n];
            t[k].siz=1;
        }
        return ;
    }
    int mid=(l+r)>>1;
    build(lk,l,mid);
    build(rk,mid+1,r);
    update(k);
}
void change(int k,int x,int y){
    if(t[k].l==t[k].r){
        t[k].w=y;
        if(!t[k].w)t[k].siz=0;//编号为0,即没有书
        else t[k].siz=1;
        return ;
    }
    int mid=(t[k].l+t[k].r)>>1;
    if(x<=mid)change(lk,x,y);
    else change(rk,x,y);
    update(k);
}
int qask(int k,int l,int r){//求[l,r]中有几本书
    if(l<=t[k].l and r>=t[k].r){
        return t[k].siz;
    }
    int mid=(t[k].l+t[k].r)>>1,ans=0;
    if(l<=mid)ans+=qask(lk,l,r);
    if(r>mid)ans+=qask(rk,l,r);
    return ans;
}
int kth(int k,int s){//求从最上面起第s本书的编号。
    if(t[k].l==t[k].r){
        return t[k].w;
    }
    if(t[lk].siz<s){//要求本数的大于左子树的大小,说明在右子树
        return kth(rk,s-t[lk].siz);//记得减掉左子树的大小
    }
    else{
        return kth(lk,s);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        id[a[i]]=i+2*n;//实际上编号为a[i]的书放的位置
    }
    build(1,1,5*n);
    ls=2*n+1,rs=3*n;
    for(int i=1;i<=m;i++){
        scanf("%s",op);
        if(op[0]=='T'){
            scanf("%d",&tx);
            change(1,id[tx],0);//移开这个位置
            id[tx]=--ls;
            change(1,id[tx],tx);//移到顶端
        }
        else if(op[0]=='B'){
            scanf("%d",&tx);
            change(1,id[tx],0);//移开这个位置
            id[tx]=++rs;
            change(1,id[tx],tx);//移到底部
        }
        else if(op[0]=='I'){
            scanf("%d%d",&tx,&ty);
            tmp=qask(1,ls,id[tx]);//先求tx是第几本,这样才能通过kth找到前驱后继
            an=kth(1,tmp+ty);//求前驱/后继
            //交换
            change(1,id[tx],an);
            change(1,id[an],tx);
            swap(id[tx],id[an]);
            //交换
        }
        else if(op[0]=='A'){
            scanf("%d",&tx);
            printf("%d\n",qask(1,ls,id[tx])-1);
        }
        else{
            scanf("%d",&tx);
            printf("%d\n",kth(1,tx));
        }
    }
    return (0^0);//完结撒花
}