黑匣子

黑匣子
(box.pas/c/cpp)
【问题描述】
某研究小组成员想发明一个黑匣子(当然不是飞机上那个),而是一个具有特殊功能的箱子。这个箱子具有两个功能:
1. 存放一些正整数 x;
2. 对于第 k 次询问,它会告诉你箱子中第 k 小的数字是多少。
但光具有理论是不够的,理论往往应联系实际。这可是一个大大的难题,没有 丰富程序设计知识的同学们希望你能帮助他们写出这个程序代码,以便他们能完成黑匣子的制作。
【输入格式】
第一行,一个数字 n 表示对黑匣子的操作次数。
以下 n 行,每行一个数字。若这个数字是正整数,则表示在黑匣子中添加这个数字;若这个数字是-1,这表示一次询问。

【输出格式】
若干行,对应每次询问所得的结果(必定存在答案)

【样例输入】
8
1
-1
8
8
-1
5
-1
-1

【样例输出】
1
8
8
8
【样例说明】
第一次询问时,黑匣子内为 1,输出最小的数 1;
第二次询问时,黑匣子内为 1 8 8,输出第二小的数 8;
第三次询问时,黑匣子内为 1 5 8 8,输出第三小的数 8;
第四次询问时,黑匣子内为 1 5 8 8,输出第四小的数 8。
【数据范围】
对于30%的数据: 5≤n≤200;
对于60%的数据: 5≤n≤10000;
对于100%的数据:5≤n≤100000;n 为整数;x 为不超过 maxlongint 的正整数;

这题有多种解法。
一.二叉排序树(对于这题的数据构造方向可以不写平衡树或伸展树)
一般在一棵二叉排序树上查询第x小的数由以下几步构成:
a.从根节点向下搜索,key=x;
b.当前节点的左子树的节点数(包括根)为lch[i],右子树的节点数为rch[i],则:
(a)如果key-lch[i]小于0,则key不变,继续find(L[i]);
(b)如果key-lch[i]大于等于0,则key-=lch[i],如果此时key=0,则当前节点为整棵二叉树的第x小节点,输出,终止;否则,key>0,则find(R[i])。
那么给出一棵二叉排序树:
         4
       /    \
      2    5
    /  \   /  \
  0  2 4  9
  /  /  \
0  8  10
例如此棵二叉排序树(不一定平衡),如果我们要查询此树上第8小的数,则:
1.从根节点“4”开始向下搜索,key=8;
2.节点“4”左子树的节点数(包括根)为5,则key-=5,key>0,key=3;
3.向下搜索到“4”的右儿子“5”;
4.节点“5”左子树的节点数(包括根)为2,则key-=2,key>0,key=1;
5.向下搜索到“5”的右儿子“9”;
6.节点“9”左子树的节点数(包括根)为2,则key-=2,key<0;
7.向下搜索到“9”的右儿子“8”;
8.节点“8”左子树的节点数(包括根)为1,则key-=1,key=0。则:输出ans=8;
9.停止搜索。
那么像这样,每读入一个非-1数则将他插入二叉排序树,如果是-1,则查询的x大的数,并把x累计。那么这样这题就解下来了,不过要更稳的话,还要配AVL什么的。
如何证明这种方法的正确性?由于二叉排序树的性质——即以某个节点为根节点的二叉排序树的左子树的任意元素都小于根元素,而右子树的任意元素都大于根元素。那么,我们可以通过这种方法来跳过“一堆元素”,而不是一个元素,所以就比普通的暴搜快的多。当然,如果二叉排序树退化成一个链表,那么效率就很低了,必须要用平衡树,因为每次跳过只能跳过一个元素。那么由于二叉排序树的性质,平均效率为O(nlogn)。

 1 const maxn=100005;
 2 var n,tot,key,ret:longint;
 3     tree:array[0..maxn] of record
 4                              data,lch,rch,L,R:longint;
 5                           end;
 6 procedure init;
 7 begin
 8   readln(n);
 9 end;
10 procedure make_tree(num,data:longint);
11 begin
12   if tree[num].data=0 then tree[num].data:=data else
13   if data<=tree[num].data then begin
14                                  inc(tree[num].L);
15                                  if tree[num].lch=0 then tree[num].lch:=tot;
16                                  make_tree(tree[num].lch,data);
17                                end else 
18                                begin
19                                  inc(tree[num].R);
20                                  if tree[num].rch=0 then tree[num].rch:=tot;
21                                  make_tree(tree[num].rch,data);
22                                end;
23 end;
24 procedure find(num:longint);
25 begin
26   if num=0 then exit;
27   if key<tree[num].L+1 then find(tree[num].lch)
28                        else begin
29                               dec(key,tree[num].L+1);
30                               if key=0 then begin writeln(tree[num].data); exit; end;
31                               find(tree[num].rch);
32                             end;
33 end;
34 procedure print;
35 var i,x,now:longint;
36 begin
37   tot:=0;
38   now:=0;
39   for i:=1 to n do
40   begin
41     readln(x);
42     if x>0 then begin
43                   inc(tot);
44                   make_tree(1,x);
45                 end else 
46                 begin
47                   inc(now);
48                   key:=now;
49                   find(1);
50                 end;
51   end;
52 end;
53 begin
54   init;
55   print;
56 end.
View Code

 

二.堆的完美应用(也是个不错的解法)
用两个堆也能完美的解决这题——一个大根堆(用1表示),一个小根堆(用2表示)。当我们读入一个不为-1的数时,我们直接把它put2到小根堆里。因为根据我们的想法,我们最后的答案就是大根堆的堆顶。那么,如果一直没有到-1,那么一直put2(x),当前的heap2[1]就是目前最小的元素。当读到一个-1,则:先把小根堆的堆顶put1到大根堆。这个元素是我们当前可能要用到的。那么,我们的答案是heap1[1],那么当读入到第x个-1时,我们所求的就是第x大的元素,而在put1(get2)后,大根堆的大小刚好是x。所以此时的答案就是大根堆的堆顶。但是还有一个问题:如果heap1[1]>heap2[1]了,怎么办?此时的heap2[1]就不是第x小的元素了,所以我们需要做一件事,就是交换两个堆的堆顶,并调整两个堆。不过如果len2=0了,也就不需要调换了。换好后输出heap1[1]就行了。这也是个好方法,并且这很稳定,不过常数稍微大了一些。由于堆的性质,最坏效率为O(nlogn)。

 1 program box;
 2  const maxn=100005;
 3  var n,len1,len2:longint;
 4      heap1,heap2:array[0..maxn] of longint;
 5  procedure swap(var x,y:longint);
 6   var tem:longint;
 7    begin
 8      tem:=x;x:=y;y:=tem;
 9    end;
10  procedure put1(x:longint);//大根堆
11   var son:longint;
12    begin
13      inc(len1);heap1[len1]:=x;
14      son:=len1;
15      while (son <> 1)and(heap1[son] > heap1[son >> 1]) do
16        begin
17          swap(heap1[son],heap1[son >> 1]);
18          son:=son >> 1;
19        end;
20    end;
21  function get1:longint;
22   var son,fa:longint;
23    begin
24      get1:=heap1[1];heap1[1]:=heap1[len1];dec(len1);
25      fa:=1;
26      while 2*fa <= len1 do
27        begin
28          if (2*fa+1 > len1)or(heap1[2*fa] > heap1[2*fa+1]) then son:=2*fa
29                                                            else son:=2*fa+1;
30          if heap1[son] > heap1[fa] then begin
31                                           swap(heap1[son],heap1[fa]);
32                                           fa:=son;
33                                         end
34                                    else break;
35        end;
36    end;
37  procedure put2(x:longint);//小根堆
38   var son:longint;
39    begin
40      inc(len2);heap2[len2]:=x;
41      son:=len2;
42      while (son <> 1)and(heap2[son] < heap2[son >> 1]) do
43        begin
44          swap(heap2[son],heap2[son >> 1]);
45          son:=son >> 1;
46        end;
47    end;
48  function get2:longint;
49   var son,fa:longint;
50    begin
51      get2:=heap2[1];heap2[1]:=heap2[len2];dec(len2);
52      fa:=1;
53      while 2*fa <= len2 do
54        begin
55          if (2*fa+1 > len2)or(heap2[2*fa] < heap2[2*fa+1]) then son:=2*fa
56                                                            else son:=2*fa+1;
57          if heap2[son] < heap2[fa] then begin
58                                           swap(heap2[son],heap2[fa]);
59                                           fa:=son;
60                                         end
61                                    else break;
62        end;
63    end;
64  procedure init;
65   var i,x:longint;
66    begin
67      len1:=0;len2:=0;
68      readln(n);
69      for i:=1 to n do
70        begin
71          readln(x);
72          if x > 0 then put2(x)
73                   else begin
74                          put1(get2);
75                          while (heap1[1] > heap2[1])and(len2 > 0) do//注意细节,小心小根堆里面是空,默认是0哦
76                            begin
77                              put1(get2);put2(get1);
78                            end;
79                          writeln(heap1[1]);
80                        end;
81        end;
82    end;
83  begin
84   init;
85  end.
View Code

----------------------------------------------------------------------UPDATE--------------------------------------------------------------------------

看了一下伸展树(splay),尽管对于这题不太适合,但是还是试了试手,写了发splay(是在二叉排序树的基础上建立的)。

对与这一题,如果当前要输出第K小的,我们肯定要找到第K小的编号,然后splay(K),将K变为ROOT然后就省去了查找的部分,直接输出T[ROOT].val就可以了。问题就在于,如何将第K小的编号找出?这要用到找后继(succ)的方法。如果当前第K-1小的编号为ROOT,通过找后继的方法可找到第K小的X,然后将它调整为根,这在很大程度上提升了速度。当然,还需要注意,当还没询问过时,不能这么干哦(毕竟第一小的前面没有第0小的)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 struct Splay{
 6     int Lid,Rid,Lcnt,Rcnt,val,fa;
 7 }T[100005];
 8 int key,ROOT;
 9 bool op;
10 int read(){
11     int x=0,f=1; char ch=getchar();
12     while (ch<'0'||ch>'9'){if (ch=='-') f=-f; ch=getchar();}
13     while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
14     return x*f;
15 }
16 void Insert(int x,int v,int cnt,int ff){
17     if (!T[x].val){T[x].val=v; T[x].fa=ff; return;}
18     if (v<T[x].val){
19         T[x].Lcnt++; if (!T[x].Lid) T[x].Lid=cnt;
20         Insert(T[x].Lid,v,cnt,x);
21     }else{
22         T[x].Rcnt++; if (!T[x].Rid) T[x].Rid=cnt;
23         Insert(T[x].Rid,v,cnt,x);
24     }
25 }
26 int Search(int x){
27     if (!x) return 0;
28     if (key<T[x].Lcnt+1) Search(T[x].Lid);
29     else{
30         key-=T[x].Lcnt+1;
31         if (!key) return x;
32         Search(T[x].Rid);
33     }
34 }
35 int Search_Succ(int x){
36     if (T[x].Rid){
37         for (int y=T[x].Rid; y; y=T[y].Lid) if (!T[y].Lid) return y;
38     }else return T[x].fa;
39 }
40 void Zig(int x){
41     int y=T[x].fa; T[y].Lid=T[x].Rid;
42     if (T[x].Rid) T[T[x].Rid].fa=y;
43     T[x].fa=T[y].fa;
44     if (T[y].fa){
45         if (y==T[T[y].fa].Lid) T[T[y].fa].Lid=x; else T[T[y].fa].Rid=x;
46     }
47     T[y].fa=x,T[x].Rid=y;
48     int cnt1=T[y].Rcnt,cnt2=T[x].Lcnt;
49     T[x].Rcnt+=cnt1+1,T[y].Lcnt-=cnt2+1;
50 }
51 void Zag(int x){
52     int y=T[x].fa; T[y].Rid=T[x].Lid;
53     if (T[x].Lid) T[T[x].Lid].fa=y;
54     T[x].fa=T[y].fa;
55     if (T[y].fa){
56         if (y==T[T[y].fa].Lid) T[T[y].fa].Lid=x; else T[T[y].fa].Rid=x;
57     }
58     T[y].fa=x,T[x].Lid=y;
59     int cnt1=T[y].Lcnt,cnt2=T[x].Rcnt;
60     T[x].Lcnt+=cnt1+1,T[y].Rcnt-=cnt2+1;
61 }
62 void splay(int x){
63     for (int y=T[x].fa; y; y=T[x].fa){
64         if (!T[y].fa){if (x==T[y].Lid) Zig(x); else Zag(x); break;}
65         if (x==T[y].Lid){
66             if (y==T[T[y].fa].Lid) Zig(y),Zig(x); else Zig(x),Zag(x);
67         }else{
68             if (y==T[T[y].fa].Rid) Zag(y),Zag(x); else Zag(x),Zig(x);
69         }
70     }
71     ROOT=x;
72 }
73 int main(){
74     memset(T,0,sizeof T),ROOT=1;
75     for (int Q=read(),now=0,cnt=0; Q; Q--){
76         int x=read();
77         if (!now){
78             if (x>0) cnt++,Insert(ROOT,x,cnt,0),splay(cnt);
79             else key=++now,splay(Search(ROOT)),printf("%d\n",T[ROOT].val);
80         }else{
81             if (x>0) cnt++,Insert(ROOT,x,cnt,0);
82             else{
83                 key=++now,ROOT=Search(ROOT),printf("%d\n",T[ROOT].val),x=Search_Succ(ROOT); if (x) splay(x);
84             }
85         }
86     }
87     return 0;
88 }
View Code

 

转载于:https://www.cnblogs.com/whc200305/p/7103524.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值