【poj1442】Black Box

Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 10890   Accepted: 4446

Description

Our Black Box represents a primitive database. It can save an integer array and has a special i variable. At the initial moment Black Box is empty and i equals 0. This Black Box processes a sequence of commands (transactions). There are two types of transactions: 

ADD (x): put element x into Black Box; 
GET: increase i by 1 and give an i-minimum out of all integers containing in the Black Box. Keep in mind that i-minimum is a number located at i-th place after Black Box elements sorting by non- descending. 

Let us examine a possible sequence of 11 transactions: 

Example 1 
N Transaction i Black Box contents after transaction Answer 

      (elements are arranged by non-descending)   

1 ADD(3)      0 3   

2 GET         1 3                                    3 

3 ADD(1)      1 1, 3   

4 GET         2 1, 3                                 3 

5 ADD(-4)     2 -4, 1, 3   

6 ADD(2)      2 -4, 1, 2, 3   

7 ADD(8)      2 -4, 1, 2, 3, 8   

8 ADD(-1000)  2 -1000, -4, 1, 2, 3, 8   

9 GET         3 -1000, -4, 1, 2, 3, 8                1 

10 GET        4 -1000, -4, 1, 2, 3, 8                2 

11 ADD(2)     4 -1000, -4, 1, 2, 2, 3, 8   

It is required to work out an efficient algorithm which treats a given sequence of transactions. The maximum number of ADD and GET transactions: 30000 of each type. 


Let us describe the sequence of transactions by two integer arrays: 


1. A(1), A(2), ..., A(M): a sequence of elements which are being included into Black Box. A values are integers not exceeding 2 000 000 000 by their absolute value, M <= 30000. For the Example we have A=(3, 1, -4, 2, 8, -1000, 2). 

2. u(1), u(2), ..., u(N): a sequence setting a number of elements which are being included into Black Box at the moment of first, second, ... and N-transaction GET. For the Example we have u=(1, 2, 6, 6). 

The Black Box algorithm supposes that natural number sequence u(1), u(2), ..., u(N) is sorted in non-descending order, N <= M and for each p (1 <= p <= N) an inequality p <= u(p) <= M is valid. It follows from the fact that for the p-element of our u sequence we perform a GET transaction giving p-minimum number from our A(1), A(2), ..., A(u(p)) sequence. 


Input

Input contains (in given order): M, N, A(1), A(2), ..., A(M), u(1), u(2), ..., u(N). All numbers are divided by spaces and (or) carriage return characters.

Output

Write to the output Black Box answers sequence for a given sequence of transactions, one number each line.

Sample Input

7 4
3 1 -4 2 8 -1000 2
1 2 6 6

Sample Output

3
3
1
2

Source


【题解】

一开始看错题目,以为是把i加入到平衡树中,然后在平衡树中把i的值加1,然后调整i在平衡树中的位置,然后再输出第i小的值。。。

所以程序中多了一个删除平衡树中某个点的操作。(就当做学习了);

真正的题意是,每次询问的时候递增i就好,然后输出第i小的数(这个i并不在平衡树里面!);

用的是sbt树。

sbt树因为本身就是用size来保持平衡的,所以求第k小的数的时候很方便;

/*

关于这个程序中的删除操作。需要用递归来实现。可能删除了某一个数字之后,拿来替换的节点也要进行复杂度删除操作(但是操作是相同的);具体的实现方式看程序;

*/

【代码】

#include <cstdio>

int l[100000],r[100000],size[100000],totn = 0,key[100000],root =0;
int a[40000],b[40000];

void right_rotation(int &t) // 右旋代码。
{
    int k = l[t];//先将根节点的左儿子提取出来。
    l[t] = r[k];//然后把根节点的左儿子的右子树接在根节点的左子树位置
    r[k] = t;//然后把原来的根节点的左儿子往右上旋转代替原来的根节点成为新的根节点。
    size[k] = size[t];//新的根节点等于原来的根节点的大小
    size[t] = size[l[t]]+size[r[t]]+1;//l[t]就是被转移过来的r[k(原来的)]。然后t的右子树是没有发生变化的。
    t = k;//新的根节点变成了t。
}

void left_rotation(int &t)//左旋代码
{
    int k = r[t]; //先获取根节点的右儿子。
    r[t] = l[k];//然后用根节点的右儿子的左子树来代替根节点的右子树;
    l[k] = t;//然后把根节点的右儿子往左上的方向提上来代替原来的根节点
    size[k] = size[t];//k成为了新的根节点,它的大小和原来的根节点是一样的。
    size[t] = size[r[t]]+size[l[t]]+1;//t的左子树没有发生变化,然后右子树已经修改过了。直接加上相应的大小再加上自身即可
    t = k;//根节点发生了变化变成了k
}

void maintain(int &t,bool flag)//maintain函数,看SBT树的实现的话绕道。这里只写一下大概思路
{
    if (flag) //往左调整
    {
        if (size[l[l[t]]] > size[r[t]])//这是/型的情况
            right_rotation(t);
            else
                if (size[r[l[t]]] > size[r[t]])//这是<型的情况
                 {
                    left_rotation(l[t]);
                    right_rotation(t);
                 }
                 else
                 return;//如果都不是的话就结束递归
    }
    else
    {
        if (size[r[r[t]]] > size[l[t]])//这是型的情况
            {
                left_rotation(t);
            }
            else
                if (size[l[r[t]]] > size[l[t]])//这是>型的情况。
                    {
                        right_rotation(r[t]);
                        left_rotation(t);
                    }
                    else
                        return;
    }
    maintain(l[t],true);//可以这样理解,如果是型,原来的根节点的右子树变成了原来的根节点的右儿子的左子树
    //而原来的根节点的左子树不变。
    //那么是原来的左子树比较大还是新的右子树比较大呢?
    //当然是原来的左子树比较大,所以原来的根节点变成根节点的左儿子之后,调整的话应该是/型或<型的应该往左
    maintain(r[t],false);//往右的同理。
    maintain(t,true);//这两句是因为左右子树如果都发生了变化,需要重新维护一下根节点的子树。
    maintain(t,false);//既然不知道方向,就两个方向都试试
}

void insert(int &t,int data)//把data这个元素插入到平衡树中。
{
    if (t==0)//如果是一个之前未到达过的节点则直接新创建一个节点
    {
        t=++totn;
        l[t] = r[t] = 0;//把这个data记录在这个位置
        size[t] = 1;//这个节点的大小为1
        key[t] = data;
    }
    else
    {
        size[t]++;//否则就判断要往哪里走,不论往哪里走,以t为根节点的树的大小肯定递增了
        if (data<key[t])//如果小于它就往左走
            insert(l[t],data);
        else//否则往右走
            insert(r[t],data);
        maintain(t,data<key[t]);//尝试调整平衡树,那个比较的表达式可以确定调整的方向。
    }
}

int k_th(int t, int k) //寻找k小数
{
    if (size[l[t]]+1 == k)//如果比key[t]小的数的数目+1就是k,那么这个key[t]就是所求的数
        return key[t];
    else
        if (size[l[t]] +1 > k)//如果比它小的数+1大于了k,那么就继续往左找第k小数
            return k_th(l[t],k);
        else
            if (size[l[t]]+1<k)//如果比它小的数+1小于k,那么问题转换成往右找第(k-(size[l[t]]+1))小的数。
                return k_th(r[t],k-(size[l[t]]+1));
}

void de_lete(int data,int &t) //这是删除平衡树中某个节点的过程;
{
    size[t]--;//因为这个节点肯定在以t为根的树下面。所以删除后,它的大小会递减。
    if (key[t] == data)//如果找到了要删除的元素。
    {
        if (l[t] ==0 && r[t] == 0)//如果它没有左子树或右子树
            t = 0;//那么就直接把这个节点置空
        else
            if (l[t] == 0 && r[t]!=0)//如果左子树为空,右子树不为空
                t = r[t];//就把这个节点去掉,用右子树来接在下面。
            else
                if (l[t] != 0 && r[t] == 0)//如果左子树不为空,右子树为空
                    t = l[t];//则直接把这个节点去掉,把左子树接在下面。
                else
                    if (l[t]!=0 && r[t]!=0)//如果左右子树都不为空。
                    {
                        int temp = r[t];//先记录这个节点的右儿子
                        while (l[temp]) temp = l[temp];//然后这个右儿子不断地往下找左儿子。
                        //这样可以找到一个合适的key放在t的位置。
                        //它满足<=r[t],且一定小于以r[t]为根的子树中的所有节点。满足平衡树定义。
                        key[t] = key[temp];//直接把那个合适的key值赋值到这个位置。
                        de_lete(key[temp],r[t]);//然后调用递归,把那个合适的key值所在的位置的节点去掉。
                        //不能单纯就去掉这个节点,因为其可能有右子树。另外,你一直往左下走,实际上没有递减
                        //size的值。。所以要重新从r[t]开始往下走。
                        /*
                            可以这样写;
                            小数据测试过。
                            int temp = r[t];
                            int pre = temp;
                            size[temp]--;
                            while (l[temp])
                            {
                                pre = temp;
                                temp = l[temp];
                                size[temp]--;
                            }
                            while结束之后temp肯定没有左子树了。
                            就判断一下有没有右子树;
                            if (r[temp]==0 && temp!=r[t])
                                l[pre] = 0;
                                else
                                    if (r[temp] !=0)
                                        l[pre] = r[temp];
                            这样就可以不用递归了。
                        */
                    }
    }
    else
    if (data<key[t])//如果要删除的值小于这个节点的key值,则往左走
        de_lete(data,l[t]);
    else//否则往右走。
        de_lete(data,r[t]);
}

int main()
{
    //freopen("rush.txt","r",stdin);
    //freopen("rush_out.txt","w",stdout);
    int now = 0;
    int n,m;
    scanf("%d%d",&n,&m); //输入n和m
    for (int i = 1;i <= n;i++) //把两个数组都输入
        scanf("%d",&a[i]);
    for (int i = 1;i <= m;i++)
        scanf("%d",&b[i]);
    int j = 1;//这是b数组的指针。
    for (int i = 1;i <= n;i++)
    {
        insert(root,a[i]);//把ai这个元素插入到平衡树中
        while (b[j]==i)//如果要询问的是在第i个,则输出询问
        {
            now++;//递增要输出的是第几小的树。。
            printf("%d
",k_th(root,now));//输出
            j++;//指针指向下一个。
        }
    }
   /*
   下面这个东西是注释的内容。是我用来测试删除,添加和求第k小数的东西。
   scanf("%s",op);
    while (op[0]!='E')
    {
        scanf("%d",&a);
        if (op[0] == 'A')
            insert(root,a);
                else
                if (op[0] == 'F')
                    printf("%d
",k_th(root,a));
                        else
                        if (op[0] == 'D')
                                de_lete(a,root);
        scanf("%s",op);
    }
    */
	return 0;
}


原文地址:https://www.cnblogs.com/AWCXV/p/7632270.html