zoukankan      html  css  js  c++  java
  • poj2104(K-th Number)分桶法(平方分割)+线段树

    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
    ", 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
    ", nums[ub]);
        }
    }
    
    int main()
    {
        cin >> n >> m;
        for (int i = 0; i < n; ++i)
            scanf("%d", &A[i]);
        solve();
        return 0;
    }


  • 相关阅读:
    MTD NANDFLASH驱动相关知识介绍
    Java 根据当前时间获取明天、当前周的周五、当前月的最后一天
    使用 Spring 进行单元测试
    Centos下MySQL主从同步配置
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'
    CentOS 7 配置静态IP
    mysql 配置 utf8 依然乱码
    rabbitMQ Connection timed out
    CentOS 7.0,启用iptables防火墙
    linux注销、关机、重启
  • 原文地址:https://www.cnblogs.com/godweiyang/p/12203951.html
Copyright © 2011-2022 走看看