分桶法和平方分割(对区间的操作)

        分桶法:把一排物品或者平面分成桶,每个桶饭分别维护自己的内部信息,以达到高效计算的目的的方法。

        平方分割:把一排n个元素中每√n个元素分在一个桶内进行维护的方法,使对区间操作的时间复杂度降为O(√n)。

        类似线段树哦。


        给定一个n个整数的数列a1, a2, a3, ..., an和m个三元组表示的查询。对于每个查询(i, j, k)输出ai, a(i+1), ..., aj的升序排列中的第k个数。(n <= 100000, m <= 5000)

        输入 n = 7, m = 3      a = {1, 5, 2, 6, 3, 7, 4}   {(2, 5, 3), (4, 4, 1), (1, 7, 3)}

        输出 5 6 3

       

        最容易想到的是把给定的区间进行排序,然后直接求出第k个数,但m<=5000,而且n<=100000,这样会超时的啦。

        可以这样想,如果x为查询区间中的第k个数,那么他会有:

        1)在区间中不超过x的数不少于k个(有可能区间中有多个x)

        2)在区间中小于x的数有不到k个

        所以可以使用二分来对排好序的b[]数列(对a[]排好序后的数组)进行查找x是否满足上面的条件,直到找到完成查找哦。

        判断x是否满足:如果查询区间都落在一个桶的范围上,那么就拿x和a[i]到a[j]一个个比较,记录k的值; 如果查询区间在多个桶之间,那么第一个桶和最后一个桶只需遍历,其它的直接使用upper_bound(其它的桶里的元素都排好序了)。

#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;

#define MAX_N 100005

const int BUCKETS_SIZE = 1000;
int a[MAX_N], b[MAX_N];
int s[5005], e[5005], pos[5005];
vector<int> buckets[MAX_N / BUCKETS_SIZE];
int n, m;

int main() {
	int i;
	int l, r, k, tl, tr, tk;
	int left, right, mid;
	int x, tmp;

	while (scanf("%d%d", &n, &m) != EOF && n || m) {
		for (i = 0; i <= n / BUCKETS_SIZE; i++) {
			buckets[i].clear();
		}

		// 输入数列并入桶
		for (i = 1; i <= n; i++) {
			scanf("%d", &a[i]);
			buckets[i / BUCKETS_SIZE].push_back(a[i]);
			b[i] = a[i];
		}

		// 输入三元组
		for (i = 0; i < m; i++) {
			scanf("%d%d%d", &s[i], &e[i], &pos[i]);
		}

		// 排序,剩下一个桶可以不排序
		sort(b + 1, b + n + 1);
		for (i = 0; i < n / BUCKETS_SIZE; i++) {
			sort(buckets[i].begin(), buckets[i].end());
		} 

		for (i = 0; i < m; i++) {
			l = s[i], r = e[i], k = pos[i];

			left = 1, right = n + 1;
			while (left < right) {
				mid = (left + right) / 2;
				x = b[mid];
				tl = l, tr = r, tk = 0;
				
				// 在同一个桶中,遍历区间
				while (tl <= tr && tl % BUCKETS_SIZE != 0) {
					if (a[tl++] <= x) {
						++tk;
					}
				}
				while (tl <= tr && tr % BUCKETS_SIZE != 0) {
					if (a[tr--] <= x) {
						++tk;
					}
				}

				// 区间不全在一个桶中,可以从排好序的同桶中upper_bound取得
				while (tl <= tr) {
					tmp = tl / BUCKETS_SIZE;
					tk += upper_bound(buckets[tmp].begin(), buckets[tmp].end(), x) - buckets[tmp].begin();
					tl += BUCKETS_SIZE;
				}

				if (k <= tk) {
					right = mid;
				} else {
					left = mid + 1;
				}
			}

			printf("%d\n", b[right]);
		}		
	}

	return 0;
}

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

7 4
9 3 7 4 5 3 8
1 7 7
1 7 4
1 7 5
1 7 3

7 4
9 3 7 4 5 3 8
2 5 2
7 7 1
3 6 2
1 7 7


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值