poj2104(K-th Number)分桶法(平方分割)+线段树

本文介绍了解决K-次序统计查询问题的两种方法:分桶法和线段树。分桶法通过将序列划分成多个小段,并对每个段进行排序,从而在给定范围内快速找到第K个最小值。线段树则构建了一个动态的数据结构,用于在查询时高效地计算指定区间的第K个最小值。两种方法的时间复杂度不同,分桶法为O((n/b) * logb + b),线段树为O(logn),适用于大规模数据集。

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


题意:给定一个正整数序列 a,每次给出一个查询 (i, j, k),输出子序列 a[i...j]按升序排列后的第 k个数


方法一:分桶法。即用平方分割,将序列每b个放在一个“桶”里,每个桶里元素都按照升序排列。如果x是第k个数,那么区间[i, j]内不超过x的元素一定不少于k个,因此如果可以快速求出不超过x的元素有多少个的话就可以对x进行二分搜索。对于区间[i, j],里面包含完整的一个桶的子区间因为是升序排列,可以二分搜索不超过x的元素个数。而对于两端不完整的子区间,直接遍历即可。时间复杂度为O((n/b)*logb+b),那么b取多少合适呢?如果按照传统的平方分割法取b=sqrt(n),那么结果会超时。。。最好的取法是b=sqrt(nlogn),也就是b取1000左右合适。

方法二:线段树。每个节点维护对应区间升序排列的数列,初始化过程其实就是归并排序!查询时二分搜索即可。

两种方法时间分别为11s和6s,不知道大牛们1s不到是怎么优化的, 慢慢学习吧。


分桶法实现:


#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <limits>
#include <new>
#include <utility>
#include <iterator>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <ctime>
using namespace std;

const int B = 1000;     //桶的大小
const int maxn = 100010;

int n, m;
int A[maxn];
int nums[maxn];     //对A排序后的结果
vector<int> bucket[maxn/B];     //每个桶排序后的结果

void solve()
{
    for (int i = 0; i < n; ++i)
    {
        bucket[i/B].push_back(A[i]);
        nums[i] = A[i];
    }
    sort(nums, nums+n);

    //最后一个组没有排序,但是没有影响
    for (int i = 0; i < n/B; ++i)
        sort(bucket[i].begin(), bucket[i].end());

    while (m--)
    {
        //求[l, r)中的第k个数
        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) >> 1;
            int x = nums[md];
            int tl = l, tr = r, c = 0;

            //区间两端多出的部分直接遍历即可
            while (tl < tr && tl % B)
                if (A[tl++] <= x)
                    c++;
            while (tl < tr && tr % B)
                if (A[--tr] <= x)
                    c++;

            //每一个桶内都二分搜索
            while (tl < tr)
            {
                int b = tl / B;
                c += upper_bound(bucket[b].begin(), bucket[b].end(), x) - bucket[b].begin();
                tl += B;
            }
            if (c >= k)
                ub = md;
            else
                lb = md;
        }
        printf("%d\n", nums[ub]);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; ++i)
        scanf("%d", &A[i]);
    solve();
    return 0;
}


线段树实现:


#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <limits>
#include <new>
#include <utility>
#include <iterator>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <ctime>
using namespace std;

const int st_size = (1 << 18) - 1;
const int maxn = 100010;

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 = k * 2 + 1, rch = k * 2 + 2;
        init(lch, l, (l+r)>>1);
        init(rch, (l+r)>>1, 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 || i >= r)
        return 0;
    //完全包含
    if (i <= l && j >= r)
        return upper_bound(dat[k].begin(), dat[k].end(), x) - dat[k].begin();
    //对儿子递归计算
    int lc = query(i, j, x, k*2+1, l, (l+r)>>1);
    int rc = query(i, j, x, k*2+2, (l+r)>>1, r);
    return lc + rc;
}

void solve()
{
    for (int i = 0; i < n; ++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 = (ub + lb) >> 1;
            int c = query(l, r, nums[md], 0, 0, n);
            if (c >= k)
                ub = md;
            else
                lb = md;
        }
        printf("%d\n", nums[ub]);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; ++i)
        scanf("%d", &A[i]);
    solve();
    return 0;
}


标题基于SpringBoot的马术俱乐部管理系统设计与实现AI更换标题第1章引言介绍马术俱乐部管理系统的研究背景、意义、国内外研究现状、论文方及创新点。1.1研究背景与意义阐述马术俱乐部管理系统对提升俱乐部管理效率的重要性。1.2国内外研究现状析国内外马术俱乐部管理系统的发展现状及存在的问题。1.3研究方以及创新点概述本文采用的研究方,包括SpringBoot框架的应用,以及系统的创新点。第2章相关理论总结和评述与马术俱乐部管理系统相关的现有理论。2.1SpringBoot框架理论介绍SpringBoot框架的基本原理、特点及其在Web开发中的应用。2.2数据库设计理论阐述数据库设计的基本原则、方以及在管理系统中的应用。2.3马术俱乐部管理理论概述马术俱乐部管理的基本理论,包括会员管理、课程安排等。第3章系统设计详细描述马术俱乐部管理系统的设计方案,包括架构设计、功能模块设计等。3.1系统架构设计给出系统的整体架构,包括前端、后端和数据库的交互方式。3.2功能模块设计详细介绍系统的各个功能模块,如会员管理、课程管理、预约管理等。3.3数据库设计阐述数据库的设计方案,包括表结构、字段设计以及数据关系。第4章系统实现介绍马术俱乐部管理系统的实现过程,包括开发环境、编码实现等。4.1开发环境搭建介绍系统开发所需的环境,包括操作系统、开发工具等。4.2编码实现详细介绍系统各个功能模块的编码实现过程。4.3系统测试与调试阐述系统的测试方、测试用例以及调试过程。第5章系统应用与析呈现马术俱乐部管理系统的应用效果,并进行性能析。5.1系统应用情况介绍系统在马术俱乐部中的实际应用情况。5.2系统性能析从响应时间、并发处理能力等方面对系统性能进行析。5.3用户反馈与改进收集用户反馈,提出系统改进建议。第6章结论与展望总结马术俱乐部管理系统的设计与实现成果,并展望未来的研究
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法码上来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值