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;
}
原文地址:https://www.cnblogs.com/jerryRey/p/4945028.html