hdu3436 splay

题意:模拟一个队列,能够查询位置k的数是多少 和 数k在哪个位置 操作是把其中一个数提到队头


解法:用splay模拟 把所有的提到头部的操作离线 然后划分区间 每个区间对应树的一个节点

然后提到头部的操作就是将对应线段提根 然后把它的左树送给与它中序的后继节点做左子树

查询位置k的节点很方便就是通过节点所记录的子树节点个数依次走下去就可以

然后查询数k所在的位置的话 要先找到对应的段 然后再splay把对应的节点提根

要注意的是 如果没有top操作 仍然会存在一个真节点和两个伪节点 真节点对应的是[1,n]用unique会跪在这里

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define maxn 222222
#define ls ch[rt][0]
#define rs ch[rt][1]
#define rls ch[root][0]
#define rrs ch[root][1]
#define mid ((l+r)>>1)
int st[maxn],ed[maxn],num[maxn],fa[maxn],ch[maxn][2],root,N;
inline void up(int rt){num[rt]=num[ls]+num[rs]+ed[rt]-st[rt]+1;}
inline void rot(int rt){
    int f=fa[rt],side=ch[f][1]==rt,&ll=ch[rt][!side];
    fa[ll]=f,ch[f][side]=ll;
    fa[rt]=fa[f],ch[fa[f]][ch[fa[f]][1]==f]=rt;
    fa[f]=rt,ch[rt][!side]=f;
    up(f),up(rt);
}
inline void splay(int rt,int aim){
    while(fa[rt]!=aim){
        int f=fa[rt],ff=fa[f];
        if(ff==aim)rot(rt);
        if((ch[ff][1]==f)==(ch[f][1]==rt))rot(f),rot(rt);
        else rot(rt),rot(rt);
    }if(!aim)root=rt;
}
void top(int aim){
    splay(aim,0);
    int rt=rrs;
    while(ls)rt=ls;
    splay(rt,aim);
    fa[ch[aim][0]]=rt,ls=ch[aim][0],ch[aim][0]=0;
    up(rt),up(aim);
}
int query(int rt){splay(rt,0);return num[ls]+1;}
int rak(int tot){
    int rt=root;
    while(1){
        if(num[ls]<tot&&num[ls]+ed[rt]-st[rt]+1>=tot)break;
        if(num[ls]>=tot)rt=ls;
        else tot-=ed[rt]-st[rt]+1+num[ls],rt=rs;
    }
    int ans=st[rt]+tot-num[ls]-1;
    return ans;
}
int bin(int rt){
    int l=1,r=N;
    while(l<=r){
        if(st[mid]>rt)r=mid-1;
        else if(ed[mid]<rt)l=mid+1;
        else return mid;
    }
    return l;
}
int a[maxn],ops[maxn];
char op[maxn],s[111];
int main(){
    int t,n,q,cnt,k,_=0;scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&q);
        _++;
        k=0;
        for(int i=0;i<q;i++){
            scanf("%s%d",s,&cnt);
            ops[i]=cnt,op[i]=s[0];
            if(s[0]=='T')a[k++]=cnt;
        }
        
        sort(a,a+k);cnt=max((int)(unique(a,a+k)-a),1);
        
        k=1;
        if(a[0]>1)st[k]=1,ed[k++]=a[0]-1;
        
        for(int i=0;i<cnt;i++){
            st[k]=ed[k]=a[i],k++;
            if(i<cnt-1){
                if(a[i+1]-a[i]>1)st[k]=a[i]+1,ed[k++]=a[i+1]-1;
            }
            else if(a[i]<n)st[k]=a[i]+1,ed[k++]=n;
        }
        
        st[k]=++n,ed[k++]=n;
        memset(ch,0,sizeof ch);
        N=k-1;num[0]=0;
        for(int i=1;i<=N;i++){
            ch[i][0]=i-1,num[i]=num[i-1]+ed[i]-st[i]+1;
            fa[i-1]=i;
        }
        root=N,fa[N]=0;
        printf("Case %d:\n",_);
        
        for(int i=0;i<q;i++){
            if(op[i]=='T')top(bin(ops[i]));
            else if(op[i]=='Q'){
                cnt=bin(ops[i]);
                printf("%d\n",query(cnt)+ops[i]-st[cnt]);
            }
            else printf("%d\n",rak(ops[i]));
        }
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值