[bzoj1503][NOI2004]郁闷的出纳员(平衡树)

题目

传送门

题解

这道题还是比较好的
平衡树第一题,所以代码打的并不熟练
题目要求使用一种支持点的插入、删除,求名次的数据结构,平衡树当然是首选
题目中的加减操作都是对于所有员工的,我们不可能对所有的点进行修改,于是我们在开一个变量delta,用来记录所有的员工的工资的变化量,那么某个员工的实际工资就是x+delta;
然而我们考虑新加入的员工,对她加上历史的delta显然是不合适的;我们可以这样处理:
在平衡树提前插入inf和-inf
I命令:加入一个员工 我们在平衡树中加入k-minn
A命令:把每位员工的工资加上k delta加k即可
S命令:把每位员工的工资扣除k 此时我们就需要考虑会不会导致一大批员工离开;我们插入minn-delta,然后使小于minn-delta的点一起移动到根的右子树的左子树,一举消灭;
F命令:查询第k多的工资 注意是第k多,Splay操作;
还有一些小细节需要注意,+2-2等等;

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1000001;
const int inf=1e9;

int n,minn,delta,f[maxn],ch[maxn][2],cnt[maxn],size[maxn],key[maxn],rt,sz;

int read()
{
    char ch=getchar(); int now=0,f=1;
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
    while (ch>='0' && ch<='9')
    {
        now=(now<<1)+(now<<3)+ch-'0';
        ch=getchar();
    }
    return now*f;
}

void clear(int x)
{
    f[x]=ch[x][0]=ch[x][1]=cnt[x]=key[x]=size[x]=0;
}
bool get(int x) {return ch[f[x]][1]==x;}

void pushup(int x)
{
    if (!x) return;
    size[x]=cnt[x];
    if (ch[x][0]) size[x]+=size[ch[x][0]];
    if (ch[x][1]) size[x]+=size[ch[x][1]];
}

void rotate(int x)
{
    int old=f[x],oldf=f[old],which=get(x);
    ch[old][which]=ch[x][which^1]; f[ch[x][which^1]]=old;
    ch[x][which^1]=old; f[old]=x;
    f[x]=oldf;
    if (oldf) ch[oldf][ch[oldf][1]==old] = x;
    pushup(x); pushup(old);
}

void splay(int x,int goal)
{
    for (int fa; (fa=f[x])!=goal; rotate(x))//这里是不等于 
        if (f[fa]!=goal)
            rotate(get(x)==get(fa)?fa:x);
    if (goal==0) rt=x;
}

void insert(int x)
{
    if (rt==0)
    {
        sz++; key[sz]=x; rt=sz;
        cnt[rt]=size[rt]=1;
        f[rt]=ch[rt][0]=ch[rt][1]=0;
        return;
    }
    int now=rt,fa=0;
    while (1)
    {
        if (x==key[now])
        {
            cnt[now]++; pushup(fa); pushup(now); splay(now,0); return;
        }
        fa=now; now=ch[now][x>key[now]];
        if (now==0)
        {
            sz++; key[sz]=x;
            size[sz]=cnt[sz]=1;
            f[sz]=fa;
            ch[sz][0]=ch[sz][1]=0;
            ch[fa][x>key[fa]]=sz;
            pushup(fa); splay(sz,0); return;//这里错了 
        }
    }
}

int id(int x)//查询x的编号 
{
    int now=rt;
    while (1)
    {
        if (x==key[now]) return now;
        else
        {
            if (x<key[now]) now=ch[now][0];
            else now=ch[now][1];
        }
    }
}

int rnk(int x)//查询x的排名
{
    int now=rt,ans=0;
    while (1)
    {
        if (x<key[now]) now=ch[now][0];
        else
        {
            ans+=size[ch[now][0]];
            if (x==key[now])
            {
                splay(now,0); return ans+1;//找出x的排名并将其旋转到根
            }
            ans+=cnt[now];
            now=ch[now][1];
        }
    }
}

int kth(int x)//查询排名为x的数
{
    int now=rt;
    while (1)
    {
        if (ch[now][0] && x<=size[ch[now][0]])
            now=ch[now][0];
        else
        {
            int tmp=size[ch[now][0]]+cnt[now];
            if (x<=tmp) return key[now];
            x-=tmp; now=ch[now][1];
        }
    }
}

int pre()
{
    int now=ch[rt][0];
    while (ch[now][1]) now=ch[now][1];
    return now;
}
int next()
{
    int now=ch[rt][1];
    while (ch[now][0]) now=ch[now][0];
    return now;
}

void del(int x)
{
    rnk(x);//将x旋转到根
    if (cnt[rt]>1) {cnt[rt]--; pushup(rt); return;}//
    if (!ch[rt][0] && !ch[rt][1]) {clear(rt); rt=0; return;}//这里的rt和x写混了,有浪费时间 
    if (!ch[rt][0]) {
        int oldrt=rt; rt=ch[rt][1]; f[rt]=0; clear(oldrt); return;
    }
    if (!ch[rt][1]) {
        int oldrt=rt; rt=ch[rt][0]; f[rt]=0; clear(oldrt); return;
    }
    int oldrt=rt; int leftbig=pre();
    splay(leftbig,0);
    ch[rt][1]=ch[oldrt][1];
    f[ch[oldrt][1]]=rt;
    clear(oldrt);
    pushup(rt);
}

int main()
{
    n=read(); minn=read();
    int totadd=0,totnow=0,ans=0;
    char opt[10]; int k;
    insert(inf); insert(-inf);
    for (int i=1; i<=n; i++)
    {
        scanf("%s%d",opt,&k);
        if (opt[0]=='I')
        {
            if (k<minn) continue;
            insert(k-delta);
            totadd++;
        } 
        if (opt[0]=='A') delta+=k;
        if (opt[0]=='S')
        {
            delta-=k;
            insert(minn-delta);
            int a=id(-inf); int b=id(minn-delta);
            splay(a,0);
            splay(b,a);
            ch[ ch[rt][1] ][0]=0;
            del(minn-delta);
        }
        if (opt[0]=='F')
        {
            totnow=rnk(inf)-2;
            if (totnow<k) {printf("-1\n"); continue;}
            int ans=kth(totnow+2-k);
            printf("%d\n",ans+delta);//最后再加上累加值delta
        }
    }
    totnow=rnk(inf)-2;
    ans=totadd-totnow;
    printf("%d",ans);
    return 0;
}

总结

积累Splay的操作:查询区间内小于某个数的点一起删掉;
加入+-inf作为根也可以查询平衡树中元素个数的思想;
“线下”维护差值的思想;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值