题目链接:
题意:
给n个数, 每次询问一个区间, 让你输出区间中的第k大的数。
做了这道题,我对线段树的能解决的问题得认识又有提升。 这个是让求区间内的第k大, 询问比较多,暴力显然不行。 我们想用线段树解题,那么线段树应该保存什么呢? 一开始更本没想到, 看了别人的博客明白了, 原来是把线段树上的每一个区间都变成有序的, 并把这个排列的顺序存在另一个数组里, 我们只在线段树中存下这一段序列的起点和终点;之后我们就可以二分查找了。 最后的结果也是我们二分得出, 怎么得出的呢? 方法是: 线段树中每个区间都是有序的, 我们就可以用二分枚举数去查找在这个区间内比当前枚举的数小或相等的个数是多少, 我们只需要找到满足个数为K的那个最小的数;这个数就是我们要找到答案。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int maxn = 100010;
const int inf = 1e9;
struct Node
{
int l, r;
}tree[maxn<<2];
int d[maxn*100];
int top, a;
void build(int l, int r, int rt)
{
if(l == r)
{
scanf("%d", &a);
d[++top] = a;
tree[rt].l = top;
tree[rt].r = top;
return ;
}
int m = (l+r)>>1;
build(lson);
build(rson);
int ll = tree[rt<<1].l, lr = tree[rt<<1].r;
int rl = tree[rt<<1|1].l, rr = tree[rt<<1|1].r;
tree[rt].l = top+1;
//下面排序是归并排序的思想
while(ll<=lr && rl<=rr)
{
if(d[ll] <= d[rl])
d[++top] = d[ll++];
else
d[++top] = d[rl++];
}
while(ll <= lr)
d[++top] = d[ll++];
while(rl <= rr)
d[++top] = d[rl++];
tree[rt].r = top;
}
//二分查找满足的个数
int search(int l, int r, int k)
{
if(d[r] <= k)
return r-l+1;
if(d[l] > k)
return 0;
int m, ll = l, rr = r;
while(ll < rr)
{
m = (ll+rr)>>1;
if(d[m] > k)
rr = m;
else
ll = m+1;
}
return ll-l;
}
//找到我们要询问区间 如果总共有4个数, 查询1~3, 我们就查询1~2 和 3~3,这是要分开查的
int query(int L, int R, int k, int l, int r, int rt)
{
if(L<=l && r<=R)
return search(tree[rt].l, tree[rt].r, k);
int sum = 0;
int m = (l+r)>>1;
if(L <= m)
sum += query(L, R, k, lson);
if(R > m)
sum += query(L, R, k, rson);
return sum;
}
int main()
{
int n, m, sl, sr, x, y, k;
while(~scanf("%d%d", &n, &m))
{
top = 0;
build(1, n, 1);
while(m--)
{
scanf("%d%d%d", &x, &y, &k);
sl = -inf, sr = inf;
int sm, res;
while(sl < sr)
{
sm = (sl+sr)>>1;
res = query(x, y, sm, 1, n, 1);
if(res >= k)
sr = sm;
else
sl = sm + 1;
}
printf("%d\n", sl);
}
}
return 0;
}