学习总结:主席树

来源:互联网 发布:java 线程安全的集合 编辑:程序博客网 时间:2024/05/16 05:11

突然想起主席树。
依稀只记得算法的大概了,所以今天又拿出来温习了一下,毕竟学习算法是很快的,忘掉也是很快的,而算法一定是要运用的,否则也就没有存在的意义了。

首先主席树是一棵线段树,而且是一个前缀权值线段树,支持静态的查询。这个算法最大的亮点就是在原有的一棵树的基础上,如果想将一个元素插入,就根据它的权值在原有的树一路搜索,并在搜索的同时开一块新的空间,作为新的树的一个节点,然后将节点的信息继承过来,如l、r、sum等,并进行更新。这样一直递归下去,新的树就建好了。(我承认还是不太清楚,就详见代码吧)。

题目:有一个序列,m次询问,求[l,r]范围中第K大的值是多少。

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;#define M 30005int a[M],b[M];int T[M*14];//防止下标越界int n,m;int tot;struct Tree{    struct node{        int l,r,sum;    }tr[M*14];    void build(int l,int r,int &id){//本质还是线段树        id=++tot;        tr[id].sum=0;        if(l==r)return;        int md=l+r>>1;        build(l,md,tr[id].l);        build(md+1,r,tr[id].r);    }    void ins(int l,int r,int v,int ot,int &id){        id=++tot;        tr[id]=tr[ot];        tr[id].sum++;        if(l==r)return;        int md=l+r>>1;        if(v<=md)ins(l,md,v,tr[ot].l,tr[id].l);        else ins(md+1,r,v,tr[ot].r,tr[id].r);    }    int find(int l,int r,int ot,int nt,int k){        if(l==r)return l;        int cnt=tr[tr[nt].r].sum-tr[tr[ot].r].sum;        int md=l+r>>1;        if(k<=cnt)return find(md+1,r,tr[ot].r,tr[nt].r,k);        return find(l,md,tr[ot].l,tr[nt].l,k-cnt);    }}Tr;int main(){    scanf("%d %d",&n,&m);    for(int i=1;i<=n;i++){        scanf("%d",&a[i]);        b[i]=a[i];    }    sort(b+1,b+n+1);    int sz=unique(b+1,b+n+1)-b-1;    for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+sz+1,a[i])-b;    Tr.build(1,sz,T[0]);    for(int i=1;i<=n;i++)Tr.ins(1,sz,a[i],T[i-1],T[i]);    for(int i=1;i<=m;i++){        int l,r,k;        scanf("%d %d %d",&l,&r,&k);        printf("%d\n",b[Tr.find(1,sz,T[l-1],T[r],k)]);//既然是一棵前缀权值线段树,当然是类似前缀和的求法了。    }    return 0;}

其实代码就是在做一件事,对于每一个元素的插入都建一棵树,只不过大部分信息是从上一棵树转移过来的。

对于一些静态查询的题目,主席树还是有一定的奇效的(不得不说,线段树还是比较无敌(nao)的)。

最后还是希望这些厉害的算法为我所用,不要在写题目的时候只有暴力向我挥手。

3 0
原创粉丝点击