K-th Number(poj2104)线段树维护区间+平方分割+二分搜索

针对K-th Number问题,本文介绍了一种使用线段树的数据结构解决方案。通过建立归并树,实现快速查询区间内第k个数的目的。适用于程序设计竞赛等场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

来自《挑战程序设计竞赛》

1.题目原文

K-th Number
Time Limit: 20000MS Memory Limit: 65536K
Total Submissions: 50565 Accepted: 17253
Case Time Limit: 2000MS

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?" 
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given. 
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

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

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

Source

Northeastern Europe 2004, Northern Subregion

2.解题思路1:线段树

线段树中每个点维护的是对应区间排好序后的序列。建树的过程和归并排序很像,每个结点的数列就是两个儿子结点的数列合并后的结果,建树的时间复杂度是O(nlogn)。这样的线段树也叫归并树。
要计算第k大值,可以利用二分法,查询区间中比x小的数有多少个。
要计算在某个区间中不超过x的数的个数,只要递归进行就可以了。
1.所给区间与当前区间完全没有交集,返回0
2.所给的区间完全包含了当前区间,使用二分搜索法对该结点上保存的数组进行查找
3.否则对两个儿子进行递归计算即可。
由于同一深度的结点最多只访问常数个,因此可以在O(log2n)时间内求出不超过x的数的个数。整个算法的时间复杂度是O(nlogn+m(logn)^3)。

3.AC代码

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include<cmath>
#include<bitset>
#include<sstream>
#include<stack>
using namespace std;
#define INF 0x7fffffff
typedef long long ll;
const int maxn=100000+10;
const int ST_SIZE=(1<<18)-1;

//编号从0到n-1
int n,m;
int A[maxn];
int nums[maxn];//对A排序之后的结果
vector<int> dat[ST_SIZE];

//构造线段树
//k是结点的编号,与区间[l,r)
void init(int k,int l,int r)
{
    if(r-l==1){
        dat[k].push_back(A[l]);
    }
    else{
        int lch=2*k+1,rch=2*k+2;
        init(lch,l,(l+r)/2);
        init(rch,(l+r)/2,r);
        dat[k].resize(r-l);
        //利用STL中的merge函数把两个儿子的数列合并
        merge(dat[lch].begin(),dat[lch].end(),dat[rch].begin(),dat[rch].end(),dat[k].begin());
    }
}

//计算[i,j)中不超过x的数的个数
//k是结点的编号,对应区间[l,r)
int query(int i,int j,int x,int k,int l,int r)
{
    if(j<=l||r<=i){
        //完全不相交
        return 0;
    }
    else if(i<=l&&r<=j){
        //完全包含在里面
        return upper_bound(dat[k].begin(),dat[k].end(),x)-dat[k].begin();
    }
    else{
        //对两个儿子递归计算
        int lc=query(i,j,x,2*k+1,l,(l+r)/2);
        int rc=query(i,j,x,2*k+2,(l+r)/2,r);
        return lc+rc;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        scanf("%d",&A[i]);
        nums[i]=A[i];
    }
    sort(nums,nums+n);

    init(0,0,n);
    while(m--){
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        l--;
        int lb=-1,ub=n-1;
        while(ub-lb>1){
            int md=(lb+ub)/2;
            int c=query(l,r,nums[md],0,0,n);
            if(c>=k) ub=md;
            else lb=md;
        }
        printf("%d\n",nums[ub]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值