• 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 }

     

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

     

  • 相关阅读:
    Servlet常用类
    Java库使用----xstream1.3.1
    字符串处理---统计每一行字符串当中的字符“u”个数
    读写锁
    求阶乘
    Fibonacci数列
    22.2-按照升序显示不重复的单词
    22.1-在散列集上进行集合操作
    完美世界-2015校园招聘-java服务器工程师-成都站
    运用jQuery写的验证表单
  • 原文地址:https://www.cnblogs.com/ZhenghangHu/p/8995258.html
Copyright © 2020-2023  润新知