POJ2104_K-th number

题意:

给n个数,m次询问s,t,k,每次问[s, t]区间的第k大数

思路:

暴力:
排一次序,然后对于每次的询问[s, t],从头开始找那些在[s, t]的数(需要提前记录在原数组的下标),并用cnt计数,当cnt=k时就是该区间的第k大数。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 100000+5;
struct Node{
	int x, idx;
	bool operator < (const Node& rhs) const{
		return x < rhs.x;
	}
}A[maxn];

int main()
{
	freopen("in.txt","r",stdin);
	int n, m,s,t,k;
	while(scanf("%d%d",&n,&m) == 2&&n){
		for(int i = 1; i <= n; ++i){
			scanf("%d",&A[i].x);
			A[i].idx = i;
		}
		sort(A+1, A+n+1);
		while(m--){
			scanf("%d%d%d",&s,&t,&k);
			int i, cnt = 0;
			for(i = 1; i <= n; ++i){
				if(A[i].idx >= s&&A[i].idx <= t) ++cnt;
				if(cnt == k) break;
			}
			printf("%d\n", A[i].x); 
		}
	}

	return 0;
}

归并树: O(m * logn * logn * logn)
推荐:POJ2104归并树
结合归并排序和线段树,建成归并树,它的每一个节点维护一个有序区间,
在这里插入图片描述
(图片来自:https://blog.csdn.net/WhereIsHeroFrom/article/details/78969718)
一直维护到根,根的长度为n,就是排过序的原数组。
当然,一般不会说每个结点开个数组存数,经观察,每一层都包含原本的n 个数,只是顺序不同而已,所以我们可以开Tree[20][maxn]来保存,也就是说共20层,每一层n个数
那么怎么查第k大数呢?
先来处理这样一个问题:查询[a, b]区间中比数x小的个数。这个我们可以通过二分来做。
那么解决了这个问题之后,我们再来二分这个x。比如要求第k大,只要二分求出哪个数x,满足比他小的个数是k-1即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 100000+5;
int n, A[maxn], Tree[20][maxn];
 
// 根据子节点来归并根节点
void push_up(int l, int r, int level){
	int mid = (l+r) >> 1;
	int i = l, j = mid+1, k = l;
	while(i <= mid&&j <= r){
		if(Tree[level+1][i] < Tree[level+1][j]) Tree[level][k++] = Tree[level+1][i++];
		else Tree[level][k++] = Tree[level+1][j++];
	}
	while(i <= mid) Tree[level][k++] = Tree[level+1][i++];
	while(j <= r) Tree[level][k++] = Tree[level+1][j++];
} 

// 归并树建树 
void Build(int l, int r, int level){
	if(l == r){
		Tree[level][l] = A[l];
		return;
	}
	int mid = (l+r) >> 1;
	Build(l, mid, level+1);
	Build(mid+1, r, level+1);
	push_up(l, r, level);
}

int la, rb;
// 查询[la, rb]区间比val小的数有多少个 
int Query(int l, int r, int level, int val){
	if(la > r||rb < l) return 0;
	if(la <= l&&rb >= r) return lower_bound(&Tree[level][l], &Tree[level][r]+1, val) - &Tree[level][l];
	int mid = (l+r) >> 1;
	return Query(l, mid, level+1, val) + Query(mid+1, r, level+1, val);
}
int solve(int k){
	int l = 1, r = n;
	while(l <= r){
		int mid = (l+r) >> 1;
		int cnt = Query(1, n, 0, Tree[0][mid]);
		//printf("%d, cnt = %d\n", Tree[0][mid],cnt);
		if(cnt <= k) l = mid+1;
		else r = mid-1;
	}
	return Tree[0][r];
}

int main()
{
	freopen("in.txt","r",stdin);
	int m;
	while(scanf("%d%d",&n,&m) == 2&&n){
		for(int i = 1; i <= n; ++i) scanf("%d",&A[i]);
		Build(1, n, 0);
		//for(int i = 1; i <= 7; ++i) printf("%d ",Tree[0][i]);
		while(m--){
			int k;
			scanf("%d%d%d", &la,&rb,&k);
			printf("%d\n", solve(k-1)); 
		}
	}

	return 0;
}

划分树:
学习入口:划分树讲解

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f; 
typedef long long LL;
const int maxn = 100000+5;
int sorted[maxn];	//排序好的数组
int Tree[20][maxn];
int num[20][maxn];	//num[i] 表示i前面有多少个点进入左孩子

void Build(int l, int r, int level){
	if(l == r) return;
	int mid = (l+r) >> 1, isame = mid-l+1;
	for(int i = l; i <= r; ++i) if(Tree[level][i] < sorted[mid]) --isame;
	int ls = l, rs = mid+1;		// 孩子节点的起始端点 
	for(int i = l; i <= r; ++i){
		if(i == l) num[level][i] = 0;
		else num[level][i] = num[level][i-1];
		if(Tree[level][i] < sorted[mid]||(Tree[level][i] == sorted[mid]&&isame > 0)){
			Tree[level+1][ls++] = Tree[level][i];
			++num[level][i];
			if(Tree[level][i] == sorted[mid]) --isame;
		}
		else
			Tree[level+1][rs++] = Tree[level][i];
	}
	Build(l, mid, level+1);
	Build(mid+1, r, level+1);
}

int Query(int la, int rb, int l, int r, int level, int k){
	if(l == r) return Tree[level][l];
	int ly;		// ly 表示la 前面有多少元素进入左孩子
	if(la == l) ly = 0;
	else ly = num[level][la-1];
	int tolef = num[level][rb] - ly;	// 这一层la到rb之间进入左子树的有tolef个
	if(tolef >= k)
		return Query(l+ly, l+num[level][rb]-1, l, (l+r)>>1, level+1, k);
	else{
		// la-l 表示la前面有多少数,再减ly 表示这些数中去右子树的有多少个
		int rs = (l+r)/2 + 1 + (la-l-ly);
		// rb-la+1 表示la到rb有多少数,减去去左边的,剩下是去右边的。假设去右边1个,下标就是lr,所以减1
		return Query(rs, rs+rb-la+1-tolef-1, (l+r)/2+1, r, level+1, k-tolef);
	}
}

int main()
{
	freopen("in.txt","r",stdin);
	int n, m;
	while(scanf("%d%d",&n,&m) == 2&&n){
		for(int i = 1; i <= n; ++i) {
			scanf("%d", &Tree[0][i]);
			sorted[i] = Tree[0][i];
		}
		sort(sorted+1, sorted+n+1);
		Build(1, n, 0);
		//for(int i = 1; i <= 7; ++i) printf("%d ",Tree[0][i]);
		while(m--){
			int la, rb, k;
			scanf("%d%d%d", &la,&rb,&k);
			printf("%d\n", Query(la, rb, 1, n, 0, k)); 
		}
	}
	fclose(stdin);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值