可持久化线段树学习笔记

模板

#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\)大。
注意的两个点:

  1. 结点\(u\)应该在\(fa_u\)的基础上建树。
  2. \(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\)就应该尽量大。
有个很常见的二叉堆贪心技巧,就是下面的做法:

  1. 建立一个大根堆,以\(val\)为指标,每个元素是一个三元组\((L,c,val)\)表示以\(L\)为左端点选到了第\(c\)大的右端点,子段值为\(val\)
  2. 先插入每个\((i,1,val)\)
  3. 每次取出堆顶,加上它的\(val\),然后通过\(L\)查询选定区间第\(k+1\)大的\(val'\),然后插入\((L,c+1,val')\),很显然可以主席树。
  4. 执行第\(3\)个步骤\(k\)次。输出结果。
posted @ 2021-02-02 17:09  Isenthalpic  阅读(60)  评论(0编辑  收藏  举报