题解 NOI2004【郁闷的出纳员】

\[ Preface \]

之前用 treap 打,交了四遍才过。

自学了 fhq treap 后,才意识到是一道 fhq treap 板子题,直接码上,一遍就过。

本题解提供的是 fhq treap 做法(感谢 fhq 神犇),不太了解 fhq treap 的同学可以去了解一下 fhq treap ,挺好理解的。(至少要了解两种 split 和 merge 操作)
\[ Description \]
你需要维护一个可重集,支持 \(4\) 种操作:

I k 向集合内插入元素 \(k\) 。

A k 将集合内所有元素加上 \(k\) 。

S k 将集合内所有元素减去 \(k\) 。

F k 查询集合内第 \(k\) 大。

一共 \(n\) 次操作。

一开始给出一个下界 \(Minv\) ,表示集合内的所有元素必须大于等于 \(Minv\) ,在任何时刻,小于 \(Minv\) 的所有元素会被立刻删除。

最后还要输出被删除的元素个数。
\[ Solution \]
对于 I k 操作:

​ 首先先判断一下 \(k\) 是否大于等于 \(Minv\) ,之后就是经典的插入操作了。

​ 让原树按 \(k-1\) 分裂成两棵树 \(x,y\) ,新建值为 \(k\) 的节点 \(New\) ,合并 \(x,New,y\) 。

\(~\)

对于 A kS k 操作:

​ 考虑到 " AS 命令的总条数不超过 100 " ,想到了啥?暴力修改!

​ 显然集合内的所有元素加上 \(k\) 或减去 \(k\),fhq treap 树形态不变。

A k 操作就很好办了,集合内的所有元素加上 \(k\) 固然还是大于等于 \(Minv\) ,直接 \(dfs\) 暴力修改即可。

S k 操作之后还会导致一些元素小于 \(Minv\) ,这些元素在还没操作前是小于 \(Minv+k\) 的。

​ 那么我们可以将原树按 \(Minv+k-1\) 分裂成两棵树 \(x,y\) 。树 \(x\) 的所有元素在操作之后都是小于 \(Minv\) 的,那么此次被删除的元素个数即为 \(size[x]\) ,将其累加进答案,随后树 \(x\) 就没有用了,不用管它就行了。树 \(y\) 的所有元素在操作之后都是大于等于 \(Minv\) 的,直接 \(dfs\) 暴力修改,最后将树 \(y\) 当成原树就行了。

\(~\)

对于 F k 操作:

​ 经典的查询第 \(k\) 大。

​ 让原树按 \(size[root]-k\) 大小分裂成两棵树 \(x,y\) ,再让树 \(y\) 按 \(1\) 大小分裂成两棵树 \(y,z\) ,此时树 \(y\) 只有一个节点,这个节点就是我们要找的第 \(k\) 大了,最后记得合并回去。

​ 当然,在 " 按大小分裂 " 上做一点小改动,或是用一般 treap 的查询第 k 大也是可以的。

\(~\)

至此,此题得到完美解决。
\[ Code \]

#include<cstdio>
#include<cstdlib>
#include<algorithm>

#define RI register int

using namespace std;

inline int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}

const int N=100100;

int m,Minv;

int tot,root;
struct treap{
    int lc,rc;
    int val,dat;
    int size;
}t[N];

int New(int val)
{
    tot++;
    t[tot].lc=t[tot].rc=0;
    t[tot].val=val;
    t[tot].dat=rand();
    t[tot].size=1;
    return tot;
}

void upd(int p)
{
    t[p].size=t[t[p].lc].size+t[t[p].rc].size+1; 
}

void split_v(int p,int val,int &x,int &y)
{
    if(!p)
        x=y=0;
    else
    {
        if(t[p].val<=val)
            x=p,split_v(t[p].rc,val,t[p].rc,y);
        else
            y=p,split_v(t[p].lc,val,x,t[p].lc);
        upd(p);
    }
}

void split_s(int p,int size,int &x,int &y)
{
    if(!p)
        x=y=0;
    else
    {
        if(t[t[p].lc].size<size)
            x=p,split_s(t[p].rc,size-t[t[p].lc].size-1,t[p].rc,y);
        else
            y=p,split_s(t[p].lc,size,x,t[p].lc);
        upd(p);
    }
}

int merge(int p,int q)
{
    if(!p||!q)
        return p^q;
    if(t[p].dat>t[q].dat)
    {
        t[p].rc=merge(t[p].rc,q),upd(p);
        return p;
    }
    else
    {
        t[q].lc=merge(p,t[q].lc),upd(q);
        return q;
    }
}

int x,y,z;

void insert(int val)
{
    split_v(root,val-1,x,y);
    root=New(val);
    root=merge(x,root);
    root=merge(root,y);
}

void dfs(int u,int val)
{
    if(!u)return;
    t[u].val+=val;
    dfs(t[u].lc,val),dfs(t[u].rc,val);
}

int GetValByRank(int rank)
{
    if(t[root].size<rank)
        return -1;
    split_s(root,t[root].size-rank,x,y);
    split_s(y,1,y,z);
    int ans=t[y].val;
    root=merge(x,y);
    root=merge(root,z);
    return ans;
}

int ans;

int main()
{
    m=read(),Minv=read();

    while(m--)
    {
        char op[2];scanf("%s",op);
        int val=read();

        switch(op[0])
        {
            case 'I':{
                if(Minv<=val)
                    insert(val);

                break;
            }

            case 'A':{
                dfs(root,val);

                break;
            }

            case 'S':{
                split_v(root,Minv+val-1,x,root);
                ans+=t[x].size;

                dfs(root,-val);

                break;
            }

            case 'F':{
                printf("%d\n",GetValByRank(val));

                break;
            }
        }
    }

    printf("%d\n",ans);

    return 0;
}

\[ Thanks \ for \ watching \]

上一篇:[GDSOI2017]中学生数据结构题(树链剖分+fhq treap)


下一篇:Treap相关