可持久化线段树学习笔记
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int cnt,rt[N];
int n,a[N],t[N],T;
int ls[N*20],rs[N*20],dat[N*20];
void Copy(int x,int y){
ls[x]=ls[y];
rs[x]=rs[y];
dat[x]=dat[y]+1;
return;
}
int build(int l,int r){
int p=++cnt;
if(l==r)return p;
int mid=l+r>>1;
ls[p]=build(l,mid);
rs[p]=build(mid+1,r);
return p;
}
int insert(int pre,int l,int r,int x){
int p=++cnt;
Copy(p,pre);
if(l==r)return p;
int mid=l+r>>1;
if(x<=mid)ls[p]=insert(ls[pre],l,mid,x);
else rs[p]=insert(rs[pre],mid+1,r,x);
return p;
}
int query(int p,int q,int l,int r,int k){
if(l==r)return l;
int nc=dat[ls[q]]-dat[ls[p]],ret;
int mid=l+r>>1;
if(nc>=k)ret=query(ls[p],ls[q],l,mid,k);
else ret=query(rs[p],rs[q],mid+1,r,k-nc);
return ret;
}
int main(){
scanf("%d%d",&n,&T);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
t[i]=a[i];
}
sort(t+1,t+n+1);
int m=unique(t+1,t+n+1)-(t+1);
rt[0]=build(1,m);
for(int i=1;i<=n;i++){
int x=lower_bound(t+1,t+m+1,a[i])-t;
rt[i]=insert(rt[i-1],1,m,x);
}
while(T--){
int L,R,K;
scanf("%d%d%d",&L,&R,&K);
printf("%d\n",t[query(rt[L-1],rt[R],1,m,K)]);
}
return 0;
}
可持久化线段树
共用节点的\(n\)棵线段树。
可持久化权值线段树也称主席树
例题
好了,你现在已经学会主席树的全部内容)),让我们来口胡几道简单题吧。
题目链接:区间第k大
全局想必大家都会,权值线段树的板子。
当询问区间的时候,
我们建立\(n\)棵权值线段树,第\(i\)棵表示\(1\)~\(i\)的权值线段树。
利用前缀和的思想,用第\(r\)棵减去第\(l\)-\(1\)棵便能求出\(l\)~\(r\)的权值情况。
所以我们直接建主席树来表示这\(n\)棵线段树就好了。
题目链接:[POI2014]KUR-Couriers
把上一题求答案的函数改改就行。
题目链接:Dynamic Rankings
带修改的第\(k\)大,在静态第\(k\)大中的主席树外套个树状数组即可。
平衡树套线段树也行。
题目链接:Count on a tree
树上第\(k\)大。
注意的两个点:
- 结点\(u\)应该在\(fa_u\)的基础上建树。
- \(lca\)求距离时是这样的:\(dis_u+dis_v\)\(-\)\(2\times dis_{lca_{u,v}}\),这个题应该是\(dis_u+dis_v\)\(-\) \(dis_{lca_{u,v}}\) \(-\) \(dis_{fa_{lca_{u,v}}}\)。
题目链接:Middle
不错的题目,不是建权值线段树了,Middle题解
题目链接:[SDOI2013]森林
树上第\(k\)大加启发式合并。
题目链接:CF840D Destiny
在\(luogu\)上黑了。。其实是个\(sb\)题。
题目链接:[CQOI2015]任务查询系统
差分一发,然后统计前缀和,线段树合并就好了。
题目链接:[NOI2010] 超级钢琴
先求前缀和。
因为子段长度有\(L\)和\(R\)的限制,如果固定左端点\(L\),右端点\(R\)就在一段固定的区间里选择。
要使\(sum_R-sum_L\)最大,那么\(sum_R\)就应该尽量大。
有个很常见的二叉堆贪心技巧,就是下面的做法:
- 建立一个大根堆,以\(val\)为指标,每个元素是一个三元组\((L,c,val)\)表示以\(L\)为左端点选到了第\(c\)大的右端点,子段值为\(val\)。
- 先插入每个\((i,1,val)\)。
- 每次取出堆顶,加上它的\(val\),然后通过\(L\)查询选定区间第\(k+1\)大的\(val'\),然后插入\((L,c+1,val')\),很显然可以主席树。
- 执行第\(3\)个步骤\(k\)次。输出结果。