浅谈主席树和区间第k值

来源:互联网 发布:com.au域名注册 编辑:程序博客网 时间:2024/06/14 19:38

主席树,是一种高级数据结构,是线段树的高级形式
主席树的全名应该叫做可持久化线段树
顾名思义,这种数据结构可以持久化,也即可查询历史记录
至于为什么要把这种数据结构叫做主席树,我就不多说了(我是不会告诉你我其实也不知道啊
首先声明:本贴中的修改指单点修改


我们先来说说主席树的来源吧。
一开始的关于记录历史记录(也就是可持久化)的做法是:建多个线段树分别存储,然后直接找对应的线段树即可
但是这样有一个非常严重的问题:

MLE!!!

首先,建一棵线段树需要大约4n的空间,那么构造像上面的那种空间复杂度就是n^2
显然,对于n=10^5,操作数=10^5这样的级别,一棵线段树就需要大约4*10^5的空间,然后为了记录历史操作,又要建10^5棵像这样的线段树,妥妥的炸内存
后来通过研究发现,每次修改操作其实只修改了线段树上的一条链,而其他线段树结构并无改变(这个自行脑补,应该很好理解)
那么这么多没有修改的部分就这么被我们浪费掉了。。。(难怪会MLE呢)
那么问题来了,我们能够继承这些原来浪费的空间吗
答案是肯定的
我们可以在每次修改之后,把修改的那条链单独拉出来,新建节点,然后把原来的没有被修改到的节点对应地连到新的节点上去(对应是指儿子、父亲对应)
就像这样:
这里写图片描述
其中修改之后的8,9,10号节点分别对应修改之前的1,2,4号节点
我们来观察一下这棵很奇怪的树(应该是两棵连体树)
对于原树(root=1),形态本质上并没有发生改变
而对于新树(root=8),被修改的那条链发生改变,其他节点并没有发生改变
我们把两棵树分开来看一下:
这里写图片描述
这个东西的效果等同于建了两棵线段树,对吧
那么接下来的操作也和上面差不多,每次提取修改的那条链,新建节点,然后把原来的没有被修改到的节点对应地连到新的节点上去
这样空间复杂度就被完美地优化到nlogn啦
这就是主席树,实现了可持久化,并且不影响正常线段树查询操作(只不过关于左右儿子不能像原来一样直接n*2和n*2+1了,不过可以直接记录一下)
关于上面的修改代码如下:

inline void xg(int l,int r,int& nod,int la,int p){//把nod引用方便新建节点    nod=++cnt;t[nod]=新值;    lt[nod]=lt[la];rt[nod]=rt[la];//一开始先复制原来节点    if(l==r)return;    int mid=(l+r)>>1;    if(判定条件向左)xg(l,mid,lt[nod],lt[la],p);//这里左儿子被新建    else xg(mid+1,r,rt[nod],rt[la],p);//同理}

查询大体同普通线段树,注意一下左右子树即可


接下来我们来说说区间第k值
这里以第k小为例

询问第k小这种问题可以线段树也可以平衡树。。。这里还是用线段树吧,时间都是nlogn(不要和我说直接排序。。。后面还有呢
查找的方式是建一棵权值线段树,然后分别插入节点记录在节点权值范围内的数的个数
查询时比较k是否小于等于该节点的值,是则往左边走,否则往右边,直到走到底即为答案
那么如何解决带区间的问题呢?
我们可以建一棵主席树,分别记录1~i范围内所有节点的值(意义同上),i从0~n
那么l~r区间就可以表示成(1~r)-(1~l-1)区间
正确性显然
每次统计时减一下即可
只不过。。。这棵主席树不支持在中间对数进行修改

代表题:poj2104:http://poj.org/problem?id=2104
再离散化一下就好了
代码如下:

#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<cstdlib>#include<iostream>using namespace std;inline int read(){    int k=0,f=1;char ch=getchar();    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}    while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}    return k*f;}int n,m,cnt=0,a[100001],b[100001];int root[100001],t[2500001],lt[2500001],rt[2500001];inline int erfen(int x,int sum){    int l=1,r=sum,ans;    while(l<=r){        int mid=(l+r)>>1;        if(b[mid]==x)return mid;        if(b[mid]>x)r=mid-1;        else l=mid+1;    }    return 0;}inline void build(int l,int r,int& nod){    nod=++cnt;t[nod]=0;    if(l==r)return;    int mid=(l+r)>>1;    build(l,mid,lt[nod]);    build(mid+1,r,rt[nod]);}inline void xg(int l,int r,int& nod,int la,int p){    nod=++cnt;t[nod]=t[la]+1;lt[nod]=lt[la];rt[nod]=rt[la];    if(l==r)return;    int mid=(l+r)>>1;    if(p<=mid)xg(l,mid,lt[nod],lt[la],p);    else xg(mid+1,r,rt[nod],rt[la],p);}inline int s(int l,int r,int x,int y,int k){    if(l==r)return l;    int mid=(l+r)>>1,cmp=t[lt[y]]-t[lt[x]];    if(k<=cmp)return s(l,mid,lt[x],lt[y],k);    else return s(mid+1,r,rt[x],rt[y],k-cmp);}int main(){    n=read();m=read();int sum=n;    for(int i=1;i<=n;i++)a[i]=b[i]=read();    sort(b+1,b+n+1);    for(int i=1;i<=sum;i++)if(b[i]==b[i-1]){        sum--;for(int j=i;j<=sum;j++)b[j]=b[j+1];    }    build(1,sum,root[0]);    for(int i=1;i<=n;i++){        a[i]=erfen(a[i],sum);xg(1,sum,root[i],root[i-1],a[i]);    }    for(int i=1;i<=m;i++){        int x=read(),y=read(),z=read();        int k=s(1,sum,root[x-1],root[y],z);        printf("%d\n",b[k]);    }    return 0;}

如果要在中间对数进行修改,怎么办呢?
我们直接对主席树进行操作是否可行呢?
答案是可行的
首先按照暴力的思想,每次修改时直接暴力在几乎每棵主席树上按链修改,时间肯定爆炸
只不过这个暴力是可以优化的
因为修改之后对后面都有影响,这个修改也类似于前缀和之类的东西
而这个东西其实是可以用树状数组来维护的
我们对于每个数在树状数组上都建立一棵线段树,然后类似于一维树状数组一样处理
修改时先把被修改点的前缀和全部减掉原来的点,再加上新点就好啦
像这样:

    int t=erfen(a[A[i]],sum);//离散化下同    for(int j=A[i];j<=n;j+=lowbit(j))xg(1,sum,root[j],root[j],t,-1);//把这个点从树中删掉(那个-1)    a[A[i]]=B[i];//变成新点    t=erfen(B[i],sum);    for(int j=A[i];j<=n;j+=lowbit(j))xg(1,sum,root[j],root[j],t,1);//把新点加到树里面去  

统计答案时和树状数组的统计方法差不多,也就是表示区间l~r,答案是sum(1~r)-sum(1~l-1)
这个东西代替原先的t[r]-t[l-1]作为比较,其他就和上面没有修改操作的主席树查询一样啦

代表题:BZOJ1901:http://www.lydsy.com/JudgeOnline/problem.php?id=1901
没有权限号的点这里:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2112
离散化还是很鬼畜很毒瘤。。。
代码如下:

#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>#include<cstdlib>#include<iostream>using namespace std;inline int lowbit(int x){return x&(-x);}inline int read(){    int k=0,f=1;char ch=getchar();    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}    while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}    return k*f;}bool K[100001]={0};int A[100001],B[100001],C[100001];int n,m,cnt=0,a[100001],b[100001],x[1001],y[1001];int root[100001],t[2500001],lt[2500001],rt[2500001];inline int erfen(int x,int sum){    int l=1,r=sum,ans;    while(l<=r){        int mid=(l+r)>>1;        if(b[mid]==x)return mid;        if(b[mid]>x)r=mid-1;        else l=mid+1;    }    return 0;}inline void xg(int l,int r,int& nod,int la,int p,int x){    nod=++cnt;t[nod]=t[la]+x;lt[nod]=lt[la];rt[nod]=rt[la];    if(l==r)return;    int mid=(l+r)>>1;    if(p<=mid)xg(l,mid,lt[nod],lt[la],p,x);    else xg(mid+1,r,rt[nod],rt[la],p,x);}inline int s(int l,int r,int k){    if(l==r)return l;int t1=0,t2=0;    for(int i=1;i<=x[0];i++)t1+=t[lt[x[i]]];for(int i=1;i<=y[0];i++)t2+=t[lt[y[i]]];    int mid=(l+r)>>1,cmp=t2-t1;    if(k<=cmp){        for(int i=1;i<=x[0];i++)x[i]=lt[x[i]];for(int i=1;i<=y[0];i++)y[i]=lt[y[i]];        return s(l,mid,k);    }else{        for(int i=1;i<=x[0];i++)x[i]=rt[x[i]];for(int i=1;i<=y[0];i++)y[i]=rt[y[i]];        return s(mid+1,r,k-cmp);    }}int main(){    n=read();m=read();int sum=n;    for(int i=1;i<=n;i++)a[i]=b[i]=read();    char c[5];    for(int i=1;i<=m;i++){        scanf("%s",c+1);A[i]=read();B[i]=read();        if(c[1]=='Q')C[i]=read(),K[i]=1;        else b[++sum]=B[i];    }    sort(b+1,b+sum+1);    for(int i=2;i<=sum;i++)if(b[i]==b[i-1]){        sum--;for(int j=i;j<=sum;j++)b[j]=b[j+1];    }    for(int i=1;i<=n;i++){        int t=erfen(a[i],sum);        for(int j=i;j<=n;j+=lowbit(j))xg(1,sum,root[j],root[j],t,1);    }    for(int i=1;i<=m;i++){        if(K[i]){            x[0]=y[0]=0;            for(int j=A[i]-1;j>0;j-=lowbit(j))x[++x[0]]=root[j];            for(int j=B[i];j>0;j-=lowbit(j))y[++y[0]]=root[j];            int k=s(1,sum,C[i]);            printf("%d\n",b[k]);        }else{            int t=erfen(a[A[i]],sum);            for(int j=A[i];j<=n;j+=lowbit(j))xg(1,sum,root[j],root[j],t,-1);            a[A[i]]=B[i];            t=erfen(B[i],sum);            for(int j=A[i];j<=n;j+=lowbit(j))xg(1,sum,root[j],root[j],t,1);                 }    }    return 0;}
3 0
原创粉丝点击