题意:
给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;
}