zoukankan      html  css  js  c++  java
  • POJ 2104 K-th Number(分桶,线段树,主席树)

    一道比较经典的数据结构题。可以用多种方式来做。

    一,分桶法(平方分解)。

    根据数字x的大小和区间内不大于x的数字数量cnt的单调性,可知第k大数kth对应的cnt应该满足cnt≥k,

    且kth是满足条件的最小的一个,可以二分下界。

    关键在于高效找出cnt,对于每个完整的桶,排序以后二分,不完整的桶就直接暴力找。

    桶的大小设置为B,那么查询复杂度为O(n/B*log(B) + B)。 由于每个桶的不是O(1),需适当减小桶数,

    最平衡取法是令:n/B*logB = B。可以得到,B≤sqrt(n logn)。 这里log确切的常数是log2。

    我测试了一下,最后取的是B =  floor(sqrt(n*log(n)))  1072。

    桶我用的vector保存,据说,vector的capacity()不够的时候是倍增的,比较耗内存,复杂度是Θ(nlogn)的,

    如果一开始就reserve()确实会快一些。

    预处理O(nlogn),查询O(m*logn*sqrt(nlogn))

                                                                            kb         ms                                  length

    /*********************************************************
    *            ------------------                          *
    *   author AbyssalFish                                   *
    **********************************************************/
    #include<cstdio>
    #include<iostream>
    #include<string>
    #include<cstring>
    #include<queue>
    #include<vector>
    #include<stack>
    #include<vector>
    #include<map>
    #include<set>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    typedef long long ll;
    
    const int B = 1072;//1072 11047ms, 1000 11563ms, 1288.98 11657ms
    const int maxn = 1e5+5;
    
    int n, m;
    int a[maxn], num[maxn];
    
    #define PB push_back
    #define all(x) x.begin(), x.end()
    
    vector<int> buk[maxn/B+1];
    
    
    //#define LOCAL
    int main()
    {
    #ifdef LOCAL
        freopen("in.txt","r",stdin);
    #endif
        //int n = 1e5;
        //cout<<sqrt(n*log2(n))<<' '<<sqrt(n)<<' '<<sqrt(n*log(n));
        //cout<<buk[0].capacity();
        for(int i = maxn/B; i--; ) { buk[i].reserve(B+5); } //  11047 -> 10969ms
        scanf("%d%d",&n,&m);
        for(int i = 0; i < n; i++){
            scanf("%d", a+i);
            buk[i/B].PB(a[i]);
        }
        memcpy(num,a,sizeof(int)*n);
        sort(num,num+n);
        //(n-1)/b <= n/b 当 n%b != 0时候取等号,这时候不是一个完整的桶,所以最后一个桶不sort也没关系
        for(int i = n/B; i--;) sort(all(buk[i]));
        while(m--){
            int l,r,k; scanf("%d%d%d",&l,&r,&k);
            int lb = 0, ub = n-1;
            while(lb < ub){
                int md = (lb+ub)>>1;
                int x = num[md];
                int p = l-1, q = r, c = 0;
                while(p<q && p%B) if(a[p++] <= x) c++;
                while(p<q && q%B) if(a[--q] <= x) c++;
                for(int i = p/B, mi = q/B; i < mi; i++){
                    c += upper_bound(all(buk[i]),x)-buk[i].begin();
                }
                c >= k?ub = md: lb = md+1;
            }
            printf("%d
    ", num[lb]);
        }
    
        return 0;
    }

    2. 线段树

    思路同上,只是计算cnt的时候改用线段树。线段树每个结点保存有序的数组,建树过程是归并排序的完整再现,因此也叫归并树。

    更抽象来看,数值大小和位置是两个维度,查询就是询问一个空间内的点数,也就是所谓的区域树了,通过嵌套可以推广到高维。

    建树O(nlogn), 查询O(m*logn^2),树状数组二分也是可以的。

                                                                          kb           ms                                length

    /*********************************************************
    *            ------------------                          *
    *   author AbyssalFish                                   *
    **********************************************************/
    #include<cstdio>
    #include<iostream>
    #include<string>
    #include<cstring>
    #include<queue>
    #include<vector>
    #include<stack>
    #include<vector>
    #include<map>
    #include<set>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    typedef long long ll;
    
    
    #define para int d = 0, int l = 0,int r = n
    #define TEMPvar int mid = (l+r)>>1;
    #define lsn d+1, l, mid
    #define rsn d+1, mid, r
    #define insd ql<=l&&r<=qr
    const int maxn = 1e5;
    int n, m;
    int a[maxn];
    const int Log2N_ = 18;
    
    int dat[Log2N_][maxn];
    
    void build(para)
    {
        if(r-l == 1){
            dat[d][l] = a[l];
        }else {
            TEMPvar
            build(lsn);
            build(rsn);
            merge(dat[d+1]+l,dat[d+1]+mid,dat[d+1]+mid,dat[d+1]+r,dat[d]+l);
        }
    }
    
    int ql, qr, qval;
    int query(para)
    {
        if(insd){
            return upper_bound(dat[d]+l,dat[d]+r,qval) - dat[d]-l;
        }else { ////intersect
            int res = 0;
            TEMPvar
            if(ql<mid) res += query(lsn);
            if(qr>mid) res += query(rsn);
            return res;
        }
    }
    
    //#define LOCAL
    int main()
    {
    #ifdef LOCAL
        freopen("in.txt","r",stdin);
    #endif
        //cout<<(int)ceil(log2(maxn))+1;
        scanf("%d%d",&n,&m);
        for(int i = 0; i < n; i++){
            scanf("%d", a+i);
        }
        build();
        while(m--){
            int k; scanf("%d%d%d",&ql,&qr,&k); ql--;
            int lb = 0, ub = n-1;
            while(lb < ub){
                int md = (lb+ub)>>1;
                qval = dat[0][md];
                query() >= k? ub = md:lb = md+1;
            }
            printf("%d
    ", dat[0][lb]);
        }
    
        return 0;
    }

    3. 主席树。

    主席树建树的思想就是可持续化。范围下标是值域,具有前缀性,只要维护结点数量就可以直接查询kth。

    建树O(nlogn), 查询O(m*logn)。比较费内存。

                                                                          kb          ms                                length

    /*********************************************************
    *            ------------------                          *
    *   author AbyssalFish                                   *
    **********************************************************/
    #include<cstdio>
    #include<iostream>
    #include<string>
    #include<cstring>
    #include<queue>
    #include<vector>
    #include<stack>
    #include<vector>
    #include<map>
    #include<set>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    typedef long long ll;
    
    #define TEMPvar int mid = (l+r)>>1;
    #define lsn o->lc, l, mid
    #define rsn o->rc, mid+1, r
    const int maxn = 1e5+5;
    int a[maxn], rk[maxn], ith[maxn];//ith[i]是第i大元素的下标 rk[i]是i号元素的名次
    const int ST_SIZE = 18*maxn;
    struct Node
    {
        Node* lc, *rc;
        int s;
    }nd[ST_SIZE];
    Node * const nil = nd;
    Node *rt[maxn];
    int cnt;
    inline Node* newNode(Node *old)
    {
        Node *nw = nd+(++cnt);
        *nw = *old;
        return nw;
    }
    
    int qval;
    void inst(Node *&o,int l,int r)
    {
        o = newNode(o);
        o->s++;
        if(l == r) return;
        TEMPvar
        if(qval <= mid) inst(lsn);
        else inst(rsn);
    }
    
    
    int query(Node *a,Node *b,int l,int r,int k)
    {
        if(l==r) return l;
        TEMPvar
        int ts = b->lc->s - a->lc->s;
        if(ts >= k) return query(a->lc,b->lc,l,mid,k);
        else return query(a->rc,b->rc,mid+1,r,k-ts);
    }
    //[](int i,int j){ return a[i] < a[j];}
    bool cmp(int i,int j){ return a[i] < a[j];}
    //#define LOCAL
    int main()
    {
    #ifdef LOCAL
        freopen("in.txt","r",stdin);
    #endif
        //cout<<(int)ceil(log2(maxn))+1;
        int n,m; scanf("%d%d",&n,&m);
        for(int i = 0; i < n; i++) {
            scanf("%d", a+i);
            ith[i] = i;
        }
        sort(ith,ith+n,cmp);
        for(int i = 0; i < n; i++) rk[ith[i]] = i;
        rt[0] = nil;
        *nil = {nil,nil,0};
        for(int i = 1; i <= n; i++){
            rt[i] = rt[i-1];
            qval = rk[i-1];
            inst(rt[i],0,n-1);
        }
        while(m--){
            int i,j,k; scanf("%d%d%d",&i,&j,&k);
            printf("%d
    ", a[ith[query(rt[i-1],rt[j],0,n-1,k)]]);
        }
        return 0;
    }
  • 相关阅读:
    VS2012的恢复默认窗口的基本常用设置
    在 CentOS 6.4 上安装 CloudStack 4.2
    centos7上安装mysql
    myeclipse安装aptana插件
    访问WEB-INF下的JSP (转载)
    通过字节流复制大文件内容到指定的文件
    java 通过bufferedReader和bufferedWriter 拷贝文件
    Date类获取日期的方法失效的解决办法
    java中string 类型的对象间比较的学习笔记
    ubuntu下搭建nfs服务器
  • 原文地址:https://www.cnblogs.com/jerryRey/p/4945028.html
Copyright © 2011-2022 走看看