POJ 2014 K-th Number

本文介绍了一种解决区间查询第K大数问题的方法。通过使用结构体保存数据及其原始位置,实现简单高效的线性搜索策略。同时对比了复杂的二分法加线段树解决方案。

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

题目大意:给你n组数据,有m个访问,访问[s,t]区间内第k个大的数

解题报告:刚开始以为很简单,对于每一个访问直接用sort算法,但结果,你懂得,超时了!然后查了很多资料,他们都用二分法+线段树,我看了一下午还是不会,想找个比较简单的,想了半天以及看了别人的代码,我有了思路 

解题思路:首先我定义一个结构体,包含输入数据以及它的下标位置。输入数据后先排列,但保持原来下标不变。之后直接从头开始找,当找到一个数它的下标大于等于s并小于等于t时,这时找到第一个数,cnt++;当找到第k个数时,结束循环,输出第K个数

代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#define MAXN 100005
using namespace std;
struct data
{
    int pos,num;
};
data N[MAXN];
int cmp(data a,data b)
{
    return a.num<b.num;
}
int main()
{
    int i,p,a,b,k,cnt,n,m;
    scanf("%d%d",&n,&m);
    for(i=1; i<=n; i++)
    {
        scanf("%d",&N[i].num);
        N[i].pos=i;
    }
    sort(N+1,N+n+1,cmp);
    for(i=1; i<=m; i++)
    {
        scanf("%d%d%d",&a,&b,&k);
        cnt=0;
        for(p=1; p<=n; p++)
            if(a<=N[p].pos&&N[p].pos<=b)
            {
                cnt++;
                if(cnt==k)
                    break;
            }
        printf("%d\n",N[p].num);
    }
    return 0;
}


顺便附一下别人的代码,我暂时还是疑惑的,先放在这,我以后看了方便,谢谢这位牛人的代码了!!!

二分查找 + 线段树
这里线段树就是query()函数。不懂线段树的童鞋先不要急,你们姑且把它看成是二分就行了。
二分在这道题中是很重要的思想. 先初始化, b[]数组排好序就是a[0][], x=0, y=n-1. 我们先取中间的数num=a[0][mid], where mid=(x+y)/2 来测试, 我们得到num在b[lef...rig]中的排在[rankL, rankR)的位置(注意是左闭右开区间). 如果
        A. rankL<=k && k<rankR 那么数num就是所求的数;
否则根据下面更新x或y不断迭代.
B. k >= rankR 那么num较小, 也就是mid的位置较低, 于是取[x...y]区间的右边, 赋值x = mid+1;
C. y = mid;
那么num较大, 于是取[x...y]区间的左边, 赋值y = mid;
*/
/*#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int ex = 20;
const int maxn =100104;
// rankL是指示大于等于某数的最小位置, rankR是指示大于某数的最小位置
// 比如在数组(必须升序)1, 3, 3, 5, 6, 查找数3, 那么rankL=1, rankR=3; 查找数4, 那么rankL=rankR=3.
int n, m, lef, rig, k, rankL, rankR; // 在每个查询中, 查询区间b[lef...rig]的第k个数.
// a数组共有ex层, 在每一层内, 我们只对某一段进行排序. 看level_sort()函数对其进行理解.
int a[ex][maxn], b[maxn];


// 提示: 把level_sort改为归并排序可以节省时间
void level_sort(int x, int y, int dep)   // 表示在层dep中, 我们对a[dep][x...y]进行排序.
{
    if (x == y) return; // 只有一个数, 直接返回; 否则我们需要深入下一层继续排序
    sort(a[dep]+x, a[dep]+y+1);
    int mid = (x+y) / 2;
    level_sort(x, mid, dep+1);
    level_sort(mid+1, y, dep+1);
}


// 预处理
void process()
{
    for (int i=0; i < ex; i++)
    {
        memcpy(a[i], b, sizeof(int)*n);
    }
    level_sort(0, n-1, 0);
}


// binary search, 二分查找.
// 可以把它看成是两个函数合成一个, 用judge来判断.
// if (judge == 0), 那么就返回大于等于某数的最小位置(和rankL相关)
// if (judge == 1), 那么就返回大于某数的最小位置(和rankR相关)
int bin(int x, int y, int num, int dep, int judge)
{
    y++;
    int ll = x;
    while (y > x)
    {
        int mid = (x+y) / 2;
        if (a[dep][mid] - num >= judge) y = mid;
        else x = mid+1;
    }
    return x-ll;
}


// 线段树, 相当于二分. 功能: 求数num在b[l...r]中的位置[rankL, rankR)
// 我们现在处于a[dep][ll...rr]中.
void query(int ll, int rr, int l, int r, int num, int dep)
{
    if (ll==l && rr==r)   // 因为a[dep][ll...rr]是排好序的, 而我们需要查找num在b[l(=ll)...r(=rr)]的位置, 所以就可以直接二分查找求位置了.
    {
        rankL += bin(ll, rr, num, dep, 0);
        rankR += bin(ll, rr, num, dep, 1);
        return;
    }
// 否则, 我们需要深入到下一层迭代.
    if (r<ll || l>rr) return;
    int mid = (ll+rr) / 2;
    query(ll, mid, l, min(mid, r), num, dep+1);
    query(mid+1, rr, max(l, mid+1), r, num, dep+1);
}


void solve()
{
    int x = 0;
    int y = n-1;
    int mid = (x+y) / 2;
    while (true)
    {
        mid = (x+y) / 2;
        rankL=0, rankR=0;
        query(0, n-1, lef, rig, a[0][mid], 0); // 求数a[0][mid]的位置[rankL, rankR)
        if (rankL<=k && k<rankR) break; // 数a[0][mid]就是所求的数
        if (k >= rankR) x = mid+1; // a[0][mid]较小, 也就是mid的位置较低, 于是取[x...y]区间的右边, 继续迭代
        else y = mid; // a[0][mid]较大, 也就是mid的位置较高, 于是取[x...y]区间的左边, 继续迭代
    }
    printf("%d\n", a[0][mid]);
}


int main()
{
    scanf("%d%d", &n ,&m);
    for (int i=0; i < n; i++)
    {
        scanf("%d", &b[i]);
    }
    process();
    while (m--)
    {
        scanf("%d%d%d", &lef, &rig, &k);
        lef--;
        rig--;
        k--;
        solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值