[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作爲根也可以查詢平衡樹中元素個數的思想;
“線下”維護差值的思想;

發佈了180 篇原創文章 · 獲贊 74 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章