浅谈主席树和区间第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;}
- 浅谈主席树和区间第k值
- 主席树 --- 求区间第k大值
- 区间第k大值(主席树入门)
- 区间第K值——主席树详解
- 主席树模板 区间第k小
- poj2104 主席树区间第k大
- 主席树解决区间第k大
- 区间第k大(主席树)
- HDU2665 主席树原理解决静态区间第K大值问题总结 有详细图解和代码解释
- poj 2104 查询区间第k小 主席树 (递归和非递归)模板
- 主席树 求区间第k大数(可修改)
- poj 2104 区间第K小 主席树入门题
- poj 2104 区间第k大 主席树
- poj2761&&poj2104 主席树(静态区间第K大)
- zoj-2112(主席树动态求区间第k小数)
- hdu 2665 区间第K大 主席树入门
- 静态区间第k大(主席树)
- poj 2104 主席树(区间第k大)
- 2017第四届全球互联网经济大会(GIEC)值得参加吗?
- 二叉树的遍历
- ECharts纵坐标时间轴
- C++作业3
- Glide使用CircleImageView,显示图片出错的问题
- 浅谈主席树和区间第k值
- 谁的青春不似梦,谁的梦里不青春
- 如何给ViewPager的条目添加渐变动画
- C/C++使用心得:enum与int的相互转换
- ECharts纵坐标显示时间1
- PHP类数组式访问(ArrayAccess接口)
- Java 动态生成二进制字节码
- 分糖果 (蓝桥杯)
- 2017安卓面试问题总结