PKU 2104 Kth-Number

最早见到这题是在07年JSOI冬令营上,当时知道了个大概,最近才弄明白,惭愧。。。

题意很简单,询问长度为100000的数组中给定区间上的第K大值

 

1. 考虑子问题,对于给定的数x,如何求出在给定区间上比x小的数有多少个(即x的排名)

2. 如果问题1得到解决,那么我们可以通过对排序过的原数组进行二分答案,直到找到一个数y,使得它在给定区间上的排名为K

 

 

本题使用线段树的思想(更严格的说,应该是归并树),树中每个节点(线段)保存该区间内原数值的有序序列。

例如原区间上的数为 2, 5, 3 那么,最终该区间上保存的数值是2,3,5. 其实并不需要存下这么多数,只需要保留一个索引(在有序数组中两端的位置)即可。这一步其实就是一个归并排序的过程。

 

对于给定一个数,我们查询其在给定区间上的排名。如果该区间正好覆盖了线段树的一个节点,那么直接进行二分查找即可。

如果不覆盖,只需要分别递归进入子区间,最终的排名即是两个子区间排名之和。

 

以上即是本题的基本算法。在我看来,这个题目的繁琐之处在于二分查找的部分。

第一个二分查找:

这是在二分答案的时候。举例: 如果给定区间上的数是2, 5, 10, 要求第三大

如果我们二分的答案是6,7,8,9,10,算出的排名都会是3. 这个时候我们要选最大的一个。

 

第二个二分查找:

这是在给定区间上二分查找排名的时候。其实就是查找比给定数值小的数的个数。这个时候如果遇上和给定数x相同的数,应当算作比x大来处理。

 

通过这个题目我知道了归并树的应用以及二分中一些细节的处理,这是以前不曾遇到的。

具体实现可参见我的代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 100010;
  4. struct node
  5. {
  6.     int x, y;
  7.     int dep;
  8. }seg[N<<3];
  9. int sorted[18][N];
  10. int n, m;
  11. int readData() 
  12.     int ret = 0; 
  13.     char ch; 
  14.     bool dot = false,neg = false
  15.     while (((ch=getchar()) > '9' || ch < '0') && ch != '-') ; 
  16.     if (ch == '-') { 
  17.         neg = true
  18.         ch = getchar(); 
  19.     } 
  20.     do { 
  21.         ret = ret*10 + (ch-'0'); 
  22.     } while ((ch=getchar()) <= '9' && ch >= '0') ; 
  23.     return (neg? -ret : ret); 
  24. }
  25. void build(int root, int x, int y, int depth)
  26. {
  27.     seg[root] = (node){x, y, depth};
  28.     if(x != y)
  29.     {
  30.         int mid = (x + y) >> 1;
  31.         build(root * 2, x, mid, depth + 1);
  32.         build(root * 2 + 1, mid + 1, y, depth + 1);
  33.         
  34.         int i = x, j = mid + 1, t = x;
  35.         while(i <= mid || j <= y)
  36.         {
  37.             if(j > y || (i <= mid && sorted[depth+1][i] <= sorted[depth+1][j])) 
  38.                 sorted[depth][t++] = sorted[depth+1][i++];
  39.             else sorted[depth][t++] = sorted[depth+1][j++];
  40.             
  41.         }
  42.     }
  43.     else scanf("%d",&sorted[depth][x]);
  44. }
  45. int be, en, Kth;
  46. int query(int root, int x)
  47. {
  48.     if(be <= seg[root].x && en >= seg[root].y)
  49.     {
  50.         int low = seg[root].x, up = seg[root].y;
  51.         int d = seg[root].dep;
  52.         
  53.         if(x <= sorted[d][low]) return 0;
  54.         else if(x > sorted[d][up]) return seg[root].y - seg[root].x + 1;
  55.         
  56.         while(low < up)
  57.         {
  58.             int mid = (low + up + 1) >> 1;
  59.             if(sorted[d][mid] >= x) up = mid - 1;
  60.             else low = mid;
  61.         }
  62.         return low - seg[root].x + 1;
  63.     }
  64.     else
  65.     {
  66.         int ans = 0;
  67.         int mid = (seg[root].x + seg[root].y) >> 1;
  68.         if(be <= mid) ans += query(root * 2, x);
  69.         if(en > mid) ans += query(root * 2 + 1, x);
  70.         return ans;
  71.     }
  72. }
  73. int main()
  74. {
  75.     int i, j;
  76.     
  77.     n = readData(), m = readData();
  78.     
  79.     build(1, 1, n, 0);
  80.     
  81.     for(; m; m--)
  82.     {
  83.         be = readData(), en = readData(), Kth = readData();
  84.         int low = 1, up = n;
  85.         while(up > low)
  86.         {
  87.             int mid = (up + low + 1) >> 1;
  88.             if(query(1, sorted[0][mid]) >= Kth) up = mid - 1;
  89.             else low = mid;
  90.         }
  91.         printf("%d/n",sorted[0][low]);
  92.     }
  93.     return 0;
  94. }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值