POJ 2104 分桶法/线段树

题目:给一个长度为n的数组a1,a2...an,1<=n<=10^5,进行m次查询,1<=m<=5000;每次查询给i,j,k,表示[i,j]区间排序之后第k个数

用平方分割来做,取块大小b=sqrt(n)把整个数组分块,每个块进行排序使块内有序。排序的复杂度为O((n/b)*(n/b)\*logb)=O(nlogn)

然后求[i,j]区间第k大的数,[i,j]区间的首尾可能是部分块,是无序的,中间是有序的块。想着如何从分割后的块中高效找出第k大的数,对有序的块维护一个数对[l,r)表示第k大的数在本块中可能的位置,对无序的块直接遍历,然后...越想越觉得难以实现>,<

看了书后才发现,对原始[1,n]排序,直接在排序后的数组中进行二分搜索,对于数组中的x,高效求出[i,j]区间中<=x的数的个数c。整个排序后的数组可分为三部分c<k,c=k,c>k,而=k的最小的x即是在[i,j]区间第k大的数。是这么求第k大的数的...


对每一个x,在有序的块中用logb的时间求出<=x的个数,在无序的块中每个值进行判断,无序的值的个数为O(b),对每一个x求出c的复杂度为O((n/b)*logb+b)=O(sqrt(n)logn)。需要求c的x的个数为O(logn),算法总的复杂度看起来差不多是nlogn级别的,感觉应该能过。

但是呢,我在对不同的n取sqrt(n*logn)的b时超时了,反而直接对b取定值1000时能过...

用分桶法来做还是不够高效,桶的大小还是个问题...

然后呢,线段树就好多了,每个结点维护对应区间排序后的结果,找[i,j]区间第k大值得方法与分桶法类似,也是对排序后的数组进行二分搜索。但是求每个x对应的c在O(logn)的复杂度内即可解出。另外,STL的merge比自己写的有序数组合并的要快不少。

// bucket method

#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <algorithm>
#include <functional>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>

using namespace std;

#define MAX_N 100005

const int B = 1000;

int N, M;
int A[MAX_N], SA[MAX_N];
vector<int> bucket[MAX_N / B];

int main() {
  scanf("%d %d", &N, &M);
  int b = 1000;
  for (int i = 0; i < N; i++) {
    scanf("%d", A + i);
    bucket[i / b].push_back(A[i]);
    SA[i] = A[i];
  }
  sort(SA, SA + N);
  for (int i = 0; i < N / b; i++) {
    sort(bucket[i].begin(), bucket[i].end());
  }
  for (int i = 0; i < M; i++) {
    int l, r, k;
    scanf("%d %d %d", &l, &r, &k);
    int lo = 0, hi = N, mi;
    while (lo < hi) {
      mi = (lo + hi) / 2;
      int x = SA[mi];
      int tl = l - 1, tr = r, c = 0;

      while (tl < tr && tl % b != 0) if (A[tl++] <= x) c++;
      while (tl < tr && tr % b != 0) if (A[--tr] <= x) c++;

      for (int j = tl / b; j < tr / b; j++) {
        c += upper_bound(bucket[j].begin(), bucket[j].end(), x) -
              bucket[j].begin();
      }

      // smallest SA[i] >= k
      if (c < k) lo = mi + 1;
      else hi = mi;
    }
    printf("%d\n", SA[lo]);
  }
  return 0;
}
//segment tree
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <algorithm>
#include <functional>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>

using namespace std;

const int MAX_N = 100005;
const int ST_SIZE = (1 << 18) - 1;

int N, M;
int A[MAX_N], SA[MAX_N];
vector<int> dat[ST_SIZE];

void init(int k, int l, int r) {
  if (r - l == 1) {
    dat[k].push_back(A[l]);
  } else {
    int lc = 2 * k + 1, rc = 2 * k + 2;
    int m = (l + r) / 2;
    init(lc, l, m);
    init(rc, m, r);
    
    dat[k].resize(r - l);
    merge(dat[lc].begin(), dat[lc].end(),
          dat[rc].begin(), dat[rc].end(),
          dat[k].begin());
  //   int i = 0, j = 0;
  //   while (i < dat[lc].size() && j < dat[rc].size()) {
  //     if (dat[lc][i] < dat[rc][j]) {
  //       dat[k].push_back(dat[lc][i++]);
  //     } else {
  //       dat[k].push_back(dat[rc][j++]);
  //     }
  //   }
  //   while (i < dat[lc].size()) dat[k].push_back(dat[lc][i++]);
  //   while (j < dat[rc].size()) dat[k].push_back(dat[rc][j++]);
  }
}

// [a, b) [l, r)
int query(int a, int b, int x, int k, int l, int r) {
  if (b <= l || r <= a) return 0;
  int res = 0;
  if (a <= l && r <= b) {
    res += upper_bound(dat[k].begin(), dat[k].end(), x)
            - dat[k].begin();
  } else {
    int lc = 2 * k + 1, rc = 2 * k + 2;
    int m = (l + r) / 2;
    res += query(a, b, x, lc, l, m);
    res += query(a, b, x, rc, m, r);
  }
  return res;
}

int main() {
  scanf("%d %d", &N, &M);
  for (int i = 1; i <= N; i++) {
    scanf("%d", A + i);
    SA[i] = A[i];
  }
  sort(SA + 1, SA + N + 1);
  init(0, 1, N + 1);
  for (int i = 0; i < M; i++) {
    int a, b, k; scanf("%d %d %d", &a, &b, &k);
    int lo = 1, hi = N + 1, md;
    while (lo < hi) {
      md = (lo + hi) / 2;
      int ct = query(a, b + 1, SA[md], 0, 1, N + 1);
      if (ct >= k) hi = md;
      else lo = md + 1;
    }
    printf("%d\n", SA[hi]);
  }
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值