标签:
一道比较经典的数据结构题。可以用多种方式来做。
一,分桶法(平方分解)。
根据数字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\n", 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\n", 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\n", a[ith[query(rt[i-1],rt[j],0,n-1,k)]]); } return 0; }
标签:
原文地址:http://www.cnblogs.com/jerryRey/p/4945028.html