• 郁闷的出纳员 HYSBZ


    OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的
    工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好
    ,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我
    真不知道除了调工资他还做什么其它事情。工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位
    员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员
    工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘
    了一位新员工,我就得为他新建一个工资档案。老板经常到我这边来询问工资情况,他并不问具体某位员工的工资
    情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后
    告诉他答案。好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样
    ,不是很困难吧?
    Input
    第一行有两个非负整数n和min。n表示下面有多少条命令,min表示工资下界。
    接下来的n行,每行表示一条命令。命令可以是以下四种之一:
    名称 格式 作用
    I命令 I_k 新建一个工资档案,初始工资为k。
                    如果某员工的初始工资低于工资下界,他将立刻离开公司。
    A命令 A_k 把每位员工的工资加上k
    S命令 S_k 把每位员工的工资扣除k
    F命令 F_k 查询第k多的工资
    _(下划线)表示一个空格,I命令、A命令、S命令中的k是一个非负整数,F命令中的k是一个正整数。
    在初始时,可以认为公司里一个员工也没有。
    I命令的条数不超过100000 
    A命令和S命令的总条数不超过100 
    F命令的条数不超过100000 
    每次工资调整的调整量不超过1000 
    新员工的工资不超过100000
    Output
    输出行数为F命令的条数加一。
    对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,
    如果k大于目前员工的数目,则输出-1。
    输出文件的最后一行包含一个整数,为离开公司的员工的总数。
    Sample Input9 10
    I 60
    I 70
    S 50
    F 2
    I 30
    S 15
    A 5
    F 1
    F 2

    Sample Output10
    20
    -1
    2

    题解:

    我原本想的是,每位员工工资都减少,那就for循环从1到sz所有的key[]都减去这个k。增加的话也类似于这样

    如果工资少于底线的话就删除,我在原来平衡树模板中的del函数中改了一点

    最后加上去TLE了

    代码:

      1 /*
      2 注意:
      3 1、看平衡树之前你要注意,对于1 3 5 3 2这一组数据。sz的值是4,因为sz保存的是节点种类
      4    为什么要这样,因为sz涉及到要为几个点开空间
      5 
      6 2、sizes[x]保存的是以x为树根的子树上节点数量,比如x这颗子树所有节点为1,2,1.那么它的sizes[x]=3
      7    而且实际上不会有两个1放在树上。而是给1的权值数组cnt[1]加1
      8 
      9 3、对于1 3 5 3 2这一组数据。sz的值是4。那么1对应位置就是1,3对应位置就是2,5对应位置就是3,2对应位置就是4
     10    之后他们所对应的位置都不会改变
     11    在平衡树旋转的过程中只是每一个点的儿子节点ch[x][0]和ch[x][1]里面保存的值在改变
     12 */
     13 #include<stdio.h>
     14 #include<string.h>
     15 #include<algorithm>
     16 #include<iostream>
     17 using namespace std;
     18 const int maxn=1e5+10;
     19 const int INF=0x3f3f3f3f;
     20 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt;
     21 /*
     22 f[i]:i节点的父节点,cnt[i]每个点出现的次数,ch[i][0/1]:0表示左孩子,
     23 1表示右孩子, size[i]表示以i为根节点的子树的节点个数
     24 key[i]表示点i代表的数的值;sz为整棵树的节点种类数,rt表示根节点
     25 */
     26 void clears(int x) //删除x点信息
     27 {
     28     f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0;
     29 }
     30 bool get(int x) //判断x是父节点的左孩子还是右孩子
     31 {
     32     return ch[f[x]][1]==x;  //返回1就是右孩子,返回0就是左孩子
     33 }
     34 void pushup(int x)  //重新计算一下x这棵子树的节点数量
     35 {
     36     if(x)
     37     {
     38         sizes[x]=cnt[x];
     39         if(ch[x][0]) sizes[x]+=sizes[ch[x][0]];
     40         if(ch[x][1]) sizes[x]+=sizes[ch[x][1]];
     41     }
     42 }
     43 void rotates(int x)  //将x移动到他父亲的位置,并且保证树依旧平衡
     44 {
     45     int fx=f[x],ffx=f[fx],which=get(x);
     46     //x点父亲,要接受x的儿子。而且x与x父亲身份交换
     47     ch[fx][which]=ch[x][which^1];
     48     f[ch[fx][which]]=fx;
     49 
     50     ch[x][which^1]=fx;
     51     f[fx]=x;
     52 
     53     f[x]=ffx;
     54     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
     55 
     56     pushup(fx);
     57     pushup(x);
     58 }
     59 void splay(int x)  //将x移动到数根节点的位置,并且保证树依旧平衡
     60 {
     61     for(int fx; fx=f[x]; rotates(x))
     62     {
     63         if(f[fx])
     64         {
     65             rotates((get(x)==get(fx))?fx:x);
     66             //如果祖父三代连城一条线,就要从祖父哪里rotate
     67             //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文
     68         }
     69     }
     70     rt=x;
     71 }
     72 /*
     73 将x这个值插入到平衡树上面
     74 如果这个值在树上存在过,那就sz不再加1,更新一下权值即可
     75 如果这个值在树上不存在,那就sz加1,再更新一下权值
     76 
     77 sz是书上节点种类数
     78 sizes[x]是x这棵子树上有多少节点
     79 */
     80 void inserts(int x)
     81 {
     82     if(rt==0)
     83     {
     84         sz++;
     85         key[sz]=x;
     86         rt=sz;
     87         cnt[sz]=sizes[sz]=1;
     88         f[sz]=ch[sz][0]=ch[sz][1]=0;
     89         return;
     90     }
     91     int now=rt,fx=0;
     92     while(1)
     93     {
     94         if(x==key[now])
     95         {
     96             cnt[now]++;
     97             pushup(now);
     98             pushup(fx);
     99             splay(now);  //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了
    100             return;
    101         }
    102         fx=now;
    103         now=ch[now][key[now]<x];
    104         if(now==0)
    105         {
    106             sz++;
    107             sizes[sz]=cnt[sz]=1;
    108             ch[sz][0]=ch[sz][1]=0;
    109             ch[fx][x>key[fx]]=sz;  //二叉查找树特性”左大右小“
    110             f[sz]=fx;
    111             key[sz]=x;
    112             pushup(fx);
    113             splay(sz);
    114             return ;
    115         }
    116     }
    117 }
    118 /*
    119 有人问:
    120 qwq很想知道为什么find操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了
    121 
    122 原博客答:
    123 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根,
    124 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的
    125 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚
    126 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只
    127 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非
    128 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一
    129 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后
    130 复杂题目的调试也非常有益
    131 
    132 我说:
    133 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE
    134 
    135 我解释:
    136 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的
    137 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面)
    138 
    139 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。
    140 */
    141 int rnk(int x)  //查询x的排名
    142 {
    143     int now=rt,ans=0;
    144     while(1)
    145     {
    146         if(x<key[now]) now=ch[now][0];
    147         else
    148         {
    149             ans+=sizes[ch[now][0]];
    150             if(x==key[now])
    151             {
    152                 splay(now); //这个splay是为了后面函数的调用提供前提条件
    153 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根
    154                 return ans+1;
    155             }
    156             ans+=cnt[now];  //cnt代表now这个位置值(key[now])出现了几次
    157             now=ch[now][1];
    158         }
    159     }
    160 }
    161 int kth(int x)
    162 {
    163     int now=rt;
    164     while(1)
    165     {
    166         if(ch[now][0] && x<=sizes[ch[now][0]])
    167         {
    168             //满足这个条件就说明它在左子树上
    169             now=ch[now][0];
    170         }
    171         else
    172         {
    173             int temp=sizes[ch[now][0]]+cnt[now];
    174             if(x<=temp) //这个temp是now左子树权值和now节点权值之和
    175                 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了
    176             x-=temp;
    177             now=ch[now][1];
    178         }
    179     }
    180 }
    181 int pre()//由于进行splay后,x已经到了根节点的位置
    182 {
    183     //求x的前驱其实就是求x的左子树的最右边的一个结点
    184 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    185 //x的左子树的最右边的一个结点
    186     int now=ch[rt][0];
    187     while(ch[now][1]) now=ch[now][1];
    188     return now;
    189 }
    190 int next()
    191 {
    192     //求后继是求x的右子树的最左边一个结点
    193 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    194 //x的右子树的最左边一个结点
    195     int now=ch[rt][1];
    196     while(ch[now][0])  now=ch[now][0];
    197     return now;
    198 }
    199 /*
    200 删除操作是最后一个稍微有点麻烦的操作。
    201 step 1:随便find一下x。目的是:将x旋转到根。
    202 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。
    203 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。
    204 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子)
    205 剩下的就是它有两个儿子的情况。
    206 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的
    207 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。
    208 */
    209 void del(int x)
    210 {
    211     rnk(x);
    212 //    if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点
    213 //    {
    214 //        cnt[rt]--;
    215 //        pushup(rt);
    216 //        return;
    217 //    }
    218     if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点
    219     {
    220         clears(rt);
    221         rt=0;
    222         return;
    223     }
    224     if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行
    225     { //然后左儿子这棵子树变成新的平衡树
    226         int frt=rt;
    227         rt=ch[rt][1];
    228         f[rt]=0;
    229         clears(frt);
    230         return;
    231     }
    232     else if(!ch[rt][1]) //只有右儿子,和上面差不多
    233     {
    234         int frt=rt;
    235         rt=ch[rt][0];
    236         f[rt]=0;
    237         clears(frt);
    238         return;
    239     }
    240     int frt=rt;
    241     int leftbig=pre();
    242     splay(leftbig); //让前驱做新根
    243     ch[rt][1]=ch[frt][1];  //这个frt指向的还是之前的根节点
    244     /*
    245     看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值
    246     */
    247     f[ch[frt][1]]=rt;
    248     clears(frt);
    249     pushup(rt);
    250 }
    251 int main()
    252 {
    253     int n,m,sum,tot=0;
    254     scanf("%d%d",&n,&m);
    255     inserts(INF);
    256     for (int i=1; i<=n; i++)
    257     {
    258         char type[5];
    259         int k;
    260         scanf("%s%d",type,&k);
    261         if (type[0]=='I')
    262         {
    263             inserts(k);
    264             tot++;
    265         }
    266         if (type[0]=='A')
    267         {
    268             for(int i=1;i<=sz;++i)
    269             {
    270                 if(key[i]!=INF)
    271                 key[i]+=k;
    272             }
    273 
    274         }
    275         if (type[0]=='S')
    276         {
    277             for(int i=1;i<=sz;++i)
    278             {
    279                 if(key[i]!=INF)
    280                 key[i]-=k;
    281             }
    282             for(int i=1;i<=sz;++i)
    283             {
    284                 if(key[i]!=INF && key[i]<m)
    285                 {
    286                     del(key[i]);
    287                 }
    288             }
    289         }
    290         if (type[0]=='F')
    291         {
    292             sum=rnk(INF);
    293             if(sum-1<k)
    294             {
    295                 printf("-1
    ");
    296                 continue;
    297             }
    298             else
    299             {
    300                 printf("%d
    ",kth(sum-k));
    301             }
    302         }
    303     }
    304     sum=rnk(INF);
    305     //printf("%d %d
    ",tot,sum);
    306     printf("%d
    ",tot-(sum-1));
    307     return 0;
    308 }
    View Code

    正解:

    既然不能对每一个员工都这样操作,那么我们在开一个变量delta,用来记录所有的员工的工资的变化量,那么某个员工的实际工资就是x+delta;

    然而我们考虑新加入的员工,对她加上历史的delta显然是不合适的;我们可以这样处理:
    在平衡树提前插入inf和-inf
    I命令:加入一个员工 我们在平衡树中加入k-minn
    A命令:把每位员工的工资加上k delta加k即可
    S命令:把每位员工的工资扣除k  此时我们就需要考虑会不会导致一大批员工离开;我们插入minn-delta,然后使小于minn-delta的点一起移动到根的右子树的左子树,一举消灭;
    F命令:查询第k多的工资 注意是第k多,Splay操作;

    代码:

      1 /*
      2 注意:
      3 1、看平衡树之前你要注意,对于1 3 5 3 2这一组数据。sz的值是4,因为sz保存的是节点种类
      4    为什么要这样,因为sz涉及到要为几个点开空间
      5 
      6 2、sizes[x]保存的是以x为树根的子树上节点数量,比如x这颗子树所有节点为1,2,1.那么它的sizes[x]=3
      7    而且实际上不会有两个1放在树上。而是给1的权值数组cnt[1]加1
      8 
      9 3、对于1 3 5 3 2这一组数据。sz的值是4。那么1对应位置就是1,3对应位置就是2,5对应位置就是3,2对应位置就是4
     10    之后他们所对应的位置都不会改变
     11    在平衡树旋转的过程中只是每一个点的儿子节点ch[x][0]和ch[x][1]里面保存的值在改变
     12 */
     13 #include<stdio.h>
     14 #include<string.h>
     15 #include<algorithm>
     16 #include<iostream>
     17 using namespace std;
     18 const int maxn=1e5+10;
     19 const int INF=1e8;
     20 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt;
     21 /*
     22 f[i]:i节点的父节点,cnt[i]每个点出现的次数,ch[i][0/1]:0表示左孩子,
     23 1表示右孩子, size[i]表示以i为根节点的子树的节点个数
     24 key[i]表示点i代表的数的值;sz为整棵树的节点种类数,rt表示根节点
     25 */
     26 void clears(int x) //删除x点信息
     27 {
     28     f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0;
     29 }
     30 bool get(int x) //判断x是父节点的左孩子还是右孩子
     31 {
     32     return ch[f[x]][1]==x;  //返回1就是右孩子,返回0就是左孩子
     33 }
     34 void pushup(int x)  //重新计算一下x这棵子树的节点数量
     35 {
     36     if(x)
     37     {
     38         sizes[x]=cnt[x];
     39         if(ch[x][0]) sizes[x]+=sizes[ch[x][0]];
     40         if(ch[x][1]) sizes[x]+=sizes[ch[x][1]];
     41     }
     42 }
     43 void rotates(int x)  //将x移动到他父亲的位置,并且保证树依旧平衡
     44 {
     45     int fx=f[x],ffx=f[fx],which=get(x);
     46     //x点父亲,要接受x的儿子。而且x与x父亲身份交换
     47     ch[fx][which]=ch[x][which^1];
     48     f[ch[fx][which]]=fx;
     49 
     50     ch[x][which^1]=fx;
     51     f[fx]=x;
     52 
     53     f[x]=ffx;
     54     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
     55 
     56     pushup(fx);
     57     pushup(x);
     58 }
     59 //void splay(int x)  //将x移动到数根节点的位置,并且保证树依旧平衡
     60 //{
     61 //    for(int fx; fx=f[x]; rotates(x))
     62 //    {
     63 //        if(f[fx])
     64 //        {
     65 //            rotates((get(x)==get(fx))?fx:x);
     66 //            //如果祖父三代连城一条线,就要从祖父哪里rotate
     67 //            //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文
     68 //        }
     69 //    }
     70 //    rt=x;
     71 //}
     72 void splay(int x,int goal)
     73 {
     74     for (int fa; (fa=f[x])!=goal; rotates(x))//这里是不等于
     75         if (f[fa]!=goal)
     76             rotates(get(x)==get(fa)?fa:x);
     77     if (goal==0) rt=x;
     78 }
     79 /*
     80 将x这个值插入到平衡树上面
     81 如果这个值在树上存在过,那就sz不再加1,更新一下权值即可
     82 如果这个值在树上不存在,那就sz加1,再更新一下权值
     83 
     84 sz是书上节点种类数
     85 sizes[x]是x这棵子树上有多少节点
     86 */
     87 void Insert(int x)
     88 {
     89     if(rt==0)
     90     {
     91         sz++;
     92         key[sz]=x;
     93         rt=sz;
     94         cnt[sz]=sizes[sz]=1;
     95         f[sz]=ch[sz][0]=ch[sz][1]=0;
     96         return;
     97     }
     98     int now=rt,fx=0;
     99     while(1)
    100     {
    101         if(x==key[now])
    102         {
    103             cnt[now]++;
    104             pushup(now);
    105             pushup(fx);
    106             splay(now,0);  //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了
    107             return;
    108         }
    109         fx=now;
    110         now=ch[now][key[now]<x];
    111         if(now==0)
    112         {
    113             sz++;
    114             sizes[sz]=cnt[sz]=1;
    115             ch[sz][0]=ch[sz][1]=0;
    116             ch[fx][x>key[fx]]=sz;  //二叉查找树特性”左大右小“
    117             f[sz]=fx;
    118             key[sz]=x;
    119             pushup(fx);
    120             splay(sz,0);
    121             return ;
    122         }
    123     }
    124 }
    125 /*
    126 有人问:
    127 qwq很想知道为什么find操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了
    128 
    129 原博客答:
    130 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根,
    131 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的
    132 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚
    133 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只
    134 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非
    135 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一
    136 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后
    137 复杂题目的调试也非常有益
    138 
    139 我说:
    140 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE
    141 
    142 我解释:
    143 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的
    144 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面)
    145 
    146 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。
    147 */
    148 int rnk(int x)  //查询x的排名
    149 {
    150     int now=rt,ans=0;
    151     while(1)
    152     {
    153         if(x<key[now]) now=ch[now][0];
    154         else
    155         {
    156             ans+=sizes[ch[now][0]];
    157             if(x==key[now])
    158             {
    159                 splay(now,0); //这个splay是为了后面函数的调用提供前提条件
    160 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根
    161                 return ans+1;
    162             }
    163             ans+=cnt[now];  //cnt代表now这个位置值(key[now])出现了几次
    164             now=ch[now][1];
    165         }
    166     }
    167 }
    168 int kth(int x)
    169 {
    170     int now=rt;
    171     while(1)
    172     {
    173         if(ch[now][0] && x<=sizes[ch[now][0]])
    174         {
    175             //满足这个条件就说明它在左子树上
    176             now=ch[now][0];
    177         }
    178         else
    179         {
    180             int temp=sizes[ch[now][0]]+cnt[now];
    181             if(x<=temp) //这个temp是now左子树权值和now节点权值之和
    182                 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了
    183             x-=temp;
    184             now=ch[now][1];
    185         }
    186     }
    187 }
    188 int pre()//由于进行splay后,x已经到了根节点的位置
    189 {
    190     //求x的前驱其实就是求x的左子树的最右边的一个结点
    191 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    192 //x的左子树的最右边的一个结点
    193     int now=ch[rt][0];
    194     while(ch[now][1]) now=ch[now][1];
    195     return now;
    196 }
    197 int next()
    198 {
    199     //求后继是求x的右子树的最左边一个结点
    200 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    201 //x的右子树的最左边一个结点
    202     int now=ch[rt][1];
    203     while(ch[now][0])  now=ch[now][0];
    204     return now;
    205 }
    206 /*
    207 删除操作是最后一个稍微有点麻烦的操作。
    208 step 1:随便find一下x。目的是:将x旋转到根。
    209 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。
    210 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。
    211 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子)
    212 剩下的就是它有两个儿子的情况。
    213 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的
    214 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。
    215 */
    216 void del(int x)
    217 {
    218     rnk(x);
    219     if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点
    220     {
    221         cnt[rt]--;
    222         pushup(rt);
    223         return;
    224     }
    225     if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点
    226     {
    227         clears(rt);
    228         rt=0;
    229         return;
    230     }
    231     if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行
    232     { //然后左儿子这棵子树变成新的平衡树
    233         int frt=rt;
    234         rt=ch[rt][1];
    235         f[rt]=0;
    236         clears(frt);
    237         return;
    238     }
    239     else if(!ch[rt][1]) //只有右儿子,和上面差不多
    240     {
    241         int frt=rt;
    242         rt=ch[rt][0];
    243         f[rt]=0;
    244         clears(frt);
    245         return;
    246     }
    247     int frt=rt;
    248     int leftbig=pre();
    249     splay(leftbig,0); //让前驱做新根
    250     ch[rt][1]=ch[frt][1];  //这个frt指向的还是之前的根节点
    251     /*
    252     看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值
    253     */
    254     f[ch[frt][1]]=rt;
    255     clears(frt);
    256     pushup(rt);
    257 }
    258 int id(int x)//查询x的编号
    259 {
    260     int now=rt;
    261     while (1)
    262     {
    263         if (x==key[now]) return now;
    264         else
    265         {
    266             if (x<key[now]) now=ch[now][0];
    267             else now=ch[now][1];
    268         }
    269     }
    270 }
    271 /*
    272 题目中的加减操作都是对于所有员工的,我们不可能对所有的点进行修改,于是我们在开一个变量delta,用来记录
    273 所有的员工的工资的变化量,那么某个员工的实际工资就是x+delta; 
    274 然而我们考虑新加入的员工,对她加上历史的delta显然是不合适的;我们可以这样处理: 
    275 在平衡树提前插入inf和-inf 
    276 I命令:加入一个员工 我们在平衡树中加入k-minn 
    277 A命令:把每位员工的工资加上k delta加k即可 
    278 S命令:把每位员工的工资扣除k 此时我们就需要考虑会不会导致一大批员工离开;我们插入minn-delta,然后使小于
    279 minn-delta的点一起移动到根的右子树的左子树,一举消灭; 
    280 F命令:查询第k多的工资 注意是第k多,Splay操作; 
    281 还有一些小细节需要注意,+2-2等等;
    282 */
    283 int main()
    284 {
    285     int n,minn;
    286     scanf("%d%d",&n,&minn);
    287     int totadd=0,totnow=0,ans=0,delta=0;
    288     char opt[10]; int k;
    289     Insert(INF); Insert(-INF);
    290     for (int i=1; i<=n; i++)
    291     {
    292         scanf("%s%d",opt,&k);
    293         if (opt[0]=='I')
    294         {
    295             if (k<minn) continue;
    296             Insert(k-delta);  //后面插入的员工的值都减去了delta,那么后面对所有员工的判断都可以直接让
    297             totadd++;         //他们的值加上delta与minn进行比较
    298         }
    299         if (opt[0]=='A') delta+=k;
    300         if (opt[0]=='S')
    301         {
    302             delta-=k;  //每一次执行S操作都要进行判断
    303             Insert(minn-delta);
    304             int a=id(-INF); int b=id(minn-delta);
    305             splay(a,0);
    306             splay(b,a); //这样的话就是把平衡树上b这个位置移动到了平衡树顶点的位置。而且这个移动
    307             ch[ ch[rt][1] ][0]=0; //的过程中b这个顶点的左子树一直在b这个顶点的左子树(不会在旋转过程中变动)
    308             del(minn-delta);  //最后把顶点(也就是b)的左子树删除了就可以了
    309         }
    310         if (opt[0]=='F')
    311         {
    312             totnow=rnk(INF)-2;
    313             if (totnow<k) {printf("-1
    "); continue;}
    314             int ans=kth(totnow+2-k);
    315             printf("%d
    ",ans+delta);//最后再加上累加值delta
    316         }
    317     }
    318     totnow=rnk(INF)-2;
    319     ans=totadd-totnow;
    320     printf("%d",ans);
    321     return 0;
    322 }
  • 相关阅读:
    webpack loader和插件的编写原理
    vue和react原理性知识点
    详谈Javascript类与继承
    vue项目中要实现展示markdown文件[转载]
    前端知识总结--2 js部分
    前端知识总结--html
    react相关知识点总结
    优秀文章
    项目部署服务器2
    项目部署服务器
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/12218424.html
Copyright © 2020-2023  润新知