POJ 2104 K-th Number 【主席树】【不带修改】

区间第k大问题用主席树解决,也即“可持久化线段树”。

前提条件:会线段树

比如给一个长度为7的数组,值分别是5,1,4,7,3,2,6让我们在里面维护区间第k大的值。首先想一下第k大我们怎么做,最朴素的方法是O(NlogN)排一下序然后输出a[k],但实际上我们可以O(N)用权值线段树解决。权值线段树是按照值域建的线段树(普通的线段树是按照index建的),其中每个结点存的d值是该值域区间里能在数组中取到的值的数量。(值域线段树不涉及tag,两个小区间合并也是严格的d值相加,所以其实比我们会的线段树要简单)这样的话我们在权值线段树树上询问第k大就是如果p->ls->d大于等于k,就在p->ls里找;否则在p->rs里找,k修改为k-p->ls->d。一直到询问到叶子节点为止。该数组建立权值线段树如下:

这里我们注意到(2,5)的线线树与(1,7)的线段树形状是一样的,形状一样是因为左值域右值域一样均为(min a[i],max a[i]),结点上的d值不一样因为区间不一样。

然后我们想到把所有的子区间都建一个权值线段树好了,长度为n的数组建出来n^2个权值线段树,询问哪个区间第k大就在哪个线段树里找就行。比如询问(2,5)的第k大,那我们建一个线段树然后在里面询问就好了。

但这样是不行的,因为每次建树时间复杂度O(N),建n^2个就是n^3复杂度,四舍五入就是一个亿啊!然后就迎来了主席树:我们建n个权值线段树,第i棵是按1~n建的权值线段树。

index: 1 2 3 4 5 6 7

a[]:     5 1 4 7 3 2 6

开始建:

建着建着我们我们发现第i棵树可以由第i-1棵树插入a[i]得到,每次插入修改logN个结点,而不是全部,所以相同的信息被重复存储了。所以我们可以进行优化:

第一棵树照常建

第二棵树我们发现只真正需要建的只有一条链,其他的可以由上一棵上里的结点代替:

第三棵树由第二棵树插入a[3]=4得到:

这样的话分析下时间复杂度是O(N)建第一棵树加上(N-1)次插入,每次插入O(logN),所以总复杂度是O(N*logN);再分析空间复杂度是第一棵树2*N加上(N-1)*logN(每次加一条链子,不知道为什么我想到了蜡烛和皮鞭...),所以空间开20*MAXN就行了,也可以接受。

这样的话我们能logN处理(1,r)第k大的询问,那怎么处理(l,r)第k大的询问呢?

=====如果有按(l,r)建的权值线段树就好了====

===思考===思考===

我们可以通过(1,l-1)和(1,r)这两棵权值线段树得到(l,r)的权值线段树吗?

===思考===思考===

可以!

发现(2,5)线段树上每个结点的d值等于(1,5)树上对应结点d值减去(1,1)树上对应结点的d值,即V.d = Vr.d - Vl-1.d

因为对应结点意味着【值域区间】一样,那么(l,r)中特定值域区间内出现的数的次数就是(1,r)中数在当前值域下出现次数减去(1,l-1)中数在当前值域中出现次数。仔细想想很容易理解。【和前缀和思路类似,原理不一样。我们解答区间求和就是处理出前缀和,(l,r)区间的和就是sum[r]-sum[l-1] 】

那么就做完了!

 

以上是思路,具体实现可以看下面:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<map>
 4 #define MAXN 100000 
 5 using namespace std;
 6 
 7 struct node{
 8     int l,r;
 9     int d;
10     node *ls,*rs;
11 }pool[20*MAXN];
12 
13 int n,m,a[MAXN+5],b[MAXN+5];
14 map<int,int> getRank,getValue;
15 node *root[MAXN+5];
16 
17 int top;
18 node* buildT(int l,int r){//root[0] 
19     node* p = pool + (++top);
20     p->l=l; p->r=r;
21     if(l==r) return p;
22     int mid=(l+r)/2;
23     p->ls = buildT(l,mid); p->rs = buildT(mid+1,r);
24     return p;
25 }
26 
27 node* update(node* rt,int rank){//基于线段树rt的update,在里面加入rank
28     int l=rt->l; int r=rt->r; 
29     node* p = pool + (++top);
30     p->l = l; p->r = r;
31     p->d = rt->d + 1; //保证rank一定在rt的值域里
32     if(l==r) return p;
33         
34     int mid=(l+r)/2;
35     if( rank>mid ) {
36         p->ls = rt->ls;
37         p->rs = update(rt->rs,rank);
38         return p;
39     }
40     else {
41         p->rs = rt->rs;
42         p->ls = update(rt->ls,rank);
43         return p;
44     }
45 }
46 
47 int query(node* lt,node *rt,int k){//假设t是按(l,r)建立起的权值线段树的根 
48     //lt和rt的值域区间一定是一样的
49     if(lt->l==lt->r) return lt->l; //这个时候k一定是1
50     int ld = rt->ls->d - lt->ls->d;//ld是t左子树值域区间里 能取得到的数的数量 
51     
52     if(k<=ld) return query(lt->ls,rt->ls,k);//第k大数的值 在左子树的值域里
53     return query(lt->rs,rt->rs,k-ld);//我们要找的第k大,就是右区间里的第k-ld大 
54 }
55 
56 int main(){
57     cin>>n>>m;
58     for(int i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]; }
59     sort(b+1,b+1+n);
60     for(int i=1;i<=n;i++) { getRank[ b[i] ] = i; getValue[i] = b[i]; }//输入的每个数 different 
61     
62     //开始建树
63     root[0]=buildT(1,n);//值域范围是1-n
64     for(int i=1;i<=n;i++) root[i] = update( root[i-1],getRank[ a[i] ] );
65     
66     for(int i=1;i<=m;i++){
67         int l,r,k; cin>>l>>r>>k;
68         cout<< getValue[ query( root[l-1],root[r],k ) ]<<endl;
69     }
70     
71     
72     return 0;
73 }

 

 尼伯龙根里那个磅礴的雨夜,咆哮的父亲和狂奔的少年,还有炽烈燃烧的黄金瞳

 

posted @ 2018-05-05 17:11  4397  阅读(295)  评论(0编辑  收藏  举报