主席树学习笔记

       转载请注明出处,谢谢: http://blog.csdn.net/qian99/article/details/12583927

       研究了好久的主席树,终于看明白一些了,把poj2014(区间第K大)A掉了,有点泪目了……自己写写总结吧。

       主席树这个数据结构我之前就看过一次,但是看了一天也没明白,这两天又拿出来看了看,终于有些进展了(我真是太弱了)……

       主席树,还有其他的名字:可持久化线段树,函数式线段树,它涉及到函数式编程,有兴趣的同学可以查资料看看。感觉网上关于主席树的资料不算多,有些写的挺好的,但我还是有好多地方看不懂……OrzOrz。

       首先,我们先从区间第k大的问题吧(暂时只会这个,噗),这个问题给出n个数,每次询问n个数中,第i到第j个数,也就是[i,j]这个区间的第k大的数。我们先想一下如何解决这个问题,由于给出的数比较大,先把这些数离散化,然后我们可以构造n棵线段树,每棵线段树对应的区间分别是1~i这些数在这些区间中出现的个数,然后我们就可以很开心地发现,区间[i,j]里的信息可以通过对应1~i和对应1~j的这两棵线段树的信息推出来,比如想要知道区间[i,j]的第k大数,对于这两棵线段树的某一区间[L,R]来说(这里姑且用T表示这一区间,sum[T]表示这一区间数的个数),令tmp=sum[T][j].L-sum[T][i-1].L,如果tmp>=k,就说明答案在左儿子中,否则答案就是右儿子区间中第k-tmp个节点。这样就可以查找到第k大的数了。

       上面说了这么多,这么做确实可以找出第k大的数,但是这样就会发现一个严重的问题,如果你建立n棵线段树,内存肯定要爆掉了。其实我们为什么非要建立n棵线段树呢?试想一下,这n棵线段树的结构都是相同的,那么我们是不是可以利用这个性质做些什么呢?我们建一个新线段树其实就是在之前的线段树的的基础上修改其中的一个值,我们可以充分利用这个之前得版本,大体思想可以这么说:如果某一区间需要修改,那么我们就新建一个节点,来表示这个新的区间,如果不需要修改,那么我们只要使用以前的就好了。举个例子,如果对于某一个节点,我们假设要修改它的左孩子,首先新建一个对应此节点的节点(因为这个节点一定被修改了),然后递归处理它的左孩子,由于它的右孩子不需要修改,那么直接让它指向之前版本的右孩子就行了,这也是主席树最经典的想法吧……也就是在保存历史版本的同时,尽量重用它们。让我们来看看这么处理以后的效果吧,本来是要建立n棵线段树的,但是现在每次修改需要改变的节点有log(n)个,也就是每次需要新增的节点是log(n)个,总共需要的节点是nlong(n)个。

       也不知道我说没说清楚……没说清楚也没办法,主要是本人太弱。这里推荐些我学习时看的论文和博文吧,看不懂的话多看几个应该会有启发吧。还有我只是根据第k大这个问题说的,还有好多问题没有涉及……还有这只是我自己的理解,如果有错请给位大牛指出,感激不尽……

         《范浩强_wc2012谈谈各种数据结构》

         《可持久化数据结构研究》

         http://www.cnblogs.com/oyking/archive/2013/08/01/3230296.html

         http://blog.csdn.net/sprintfwater/article/details/9162041

        其实主席树的代码意外的蛮好写的,我还以为会好难写。下面附上poj2014的代码,参考了些别人的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=100000+10;
struct Node
{
    int L,R;
    int sum;
};
struct Num
{
    int v,pos;
    bool operator <(const Num &a) const
    {
        return v<a.v;
    }
};
Node node[maxn*20];
Num a[maxn];
int node_cnt;
int rank[maxn],root[maxn];
int build(int l,int r)
{
    if(l==r)
    {
        node[node_cnt++].sum=0;
        return node_cnt-1;
    }
    int m=(l+r)>>1;
    int x;
    node[node_cnt++].sum=0;
    x=node_cnt-1;
    node[x].L=build(l,m);
    node[x].R=build(m+1,r);
    return x;
}
int insert(int p,int l,int r,int rt)
{
    node[node_cnt++]=node[rt];
    int x=node_cnt-1;
    node[x].sum++;
    if(l==r)
    {
        return x;
    }
    int m=(l+r)>>1;
    if(m>=p)
      node[x].L=insert(p,l,m,node[x].L);
    else
      node[x].R=insert(p,m+1,r,node[x].R);
    return x;
}
int Query(int r1,int r2,int l,int r,int k)
{
    if(l==r) return l;
    int m=(l+r)>>1;
    int tmp=node[node[r2].L].sum-node[node[r1].L].sum;
    if(tmp>=k)
      return Query(node[r1].L,node[r2].L,l,m,k);
    else
      return Query(node[r1].R,node[r2].R,m+1,r,k-tmp);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        node_cnt=0;
        root[0]=build(1,n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&a[i].v);
            a[i].pos=i;
        }
        sort(a+1,a+n+1);
        for(int i=1;i<=n;++i)
          rank[a[i].pos]=i;
        for(int i=1;i<=n;++i)
          root[i]=insert(rank[i],1,n,root[i-1]);
        int x,y,k,ps;
        for(int i=0;i<m;++i)
        {
            scanf("%d%d%d",&x,&y,&k);
            ps=Query(root[x-1],root[y],1,n,k);
            printf("%d\n",a[ps].v);
        }
    }
    return 0;
}


 

你可能感兴趣的:(数据结构,学习笔记,主席树)