可持久化线段树学习笔记

可持久化线段树,即保存每一次修改后的版本的线段树。

首先考虑最朴素的操作:对于每次修改,将前一棵线段树复制一遍,然后在新树上进行修改。

显然这样做复制树的时间十分劣,而且空间也会爆炸。考虑优化这一过程。

容易发现,线段树上每次修改操作只会更新一条链上的值,其他点不变,所以我们复制无关的点浪费了大量时间。可以只新建修改的这条链,对于链上一个点,新建链上的下一个点,另一个儿子直接连接到上一棵树的相同位置(所以这里不能用普通线段树的乘二表示法了,只能用指针式线段树)。

这样就完成了可持久化线段树的构造。

以板子题举例:

【模板】可持久化线段树 2

先不考虑这个问题,先考虑一个弱化版:求 \([1,n]\) 中出现最多的数。

我们可以建立一棵权值线段树,存储每个值出现的次数,维护 \(\max\)

为解决这个问题,我们可以建立一棵可持久化权值线段树。

对于每个前缀,即只有前 \(i\) 个数插入的情况,建立一个版本,最直接的想法就是当查询 \([l,r]\) 时,用第 \(r\) 个版本每个值减去第 \(l-1\) 个版本对应的值,得出一个值在 \([l,r]\) 内出现的次数。

但这个做法显然不好实现,我们考虑一个思想相同,实现略有差异的做法。

对于每个版本,维护每个子树的体积,查询一个点时可以求出该点左子树 \([l,r]\) 内数的个数,(求最大就右子树),若左边点个数足够就查找左边第 \(k\) 小,不够则查右边第 \(k-siz_{left}\) 小。

代码很简单,记得插入空树。内存开 \(32\) 倍。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define debug cout<<"DEBUG"<<endl;
#define pb push_back
#define pii pair<int,int>
#define vi vector<int>
#define imp map<int,int>
using namespace std;
const int N=2e5+5;
struct node{
	int l,r,sum;
}seg[N*32];
int n,m,q,a[N],b[N],rt[N],cnt=0;
int build(int l,int r){
	int x=++cnt;
	seg[x].sum=0;
	if(l<r){
		int mid=(l+r)>>1;
		seg[x].l=build(l,mid);
		seg[x].r=build(mid+1,r);
	}
	return x;
}
int change(int pre,int l,int r,int d){
	int x=++cnt;
	seg[x].sum=seg[pre].sum+1;
	seg[x].l=seg[pre].l;
	seg[x].r=seg[pre].r;
	if(l<r){
		int mid=(l+r)>>1;
		if(d<=mid) seg[x].l=change(seg[pre].l,l,mid,d);
		else seg[x].r=change(seg[pre].r,mid+1,r,d);
	}
	return x;
}
int query(int u,int v,int l,int r,int d){
	if(l>=r) return l;
	int num=seg[seg[v].l].sum-seg[seg[u].l].sum;
	int mid=(l+r)>>1;
	if(num>=d) return query(seg[u].l,seg[v].l,l,mid,d);
	else return query(seg[u].r,seg[v].r,mid+1,r,d-num);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	m=unique(b+1,b+n+1)-b-1;
	rt[0]=build(1,m);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(b+1,b+m+1,a[i])-b;
		rt[i]=change(rt[i-1],1,m,a[i]);
	}
	while(q--){
		int x,y,z;
		cin>>x>>y>>z;
		cout<<b[query(rt[x-1],rt[y],1,m,z)]<<"\n"; 
	}
	return 0;
}

例题:

[POI2014]KUR-Couriers

跟上一题类似,只不过是求区间众数。

同样建立可持久化权值线段树,对于一个点看两边的点是否大于查询区间的长度一半,如果大于的话说明有可能存在众数。

值得一提的是这题的良心出题人为我们省去了离散化。这种人现在已经灭绝了

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define debug cout<<"DEBUG"<<endl;
#define pb push_back
#define pii pair<int,int>
#define vi vector<int>
#define imp map<int,int>
using namespace std;
const int N=5e5+5;
struct node{
	int l,r,sum;
}seg[N*32];
int n,m,q,a[N],b[N],rt[N],cnt=0;
int build(int l,int r){
	int x=++cnt;
	seg[x].sum=0;
	if(l<r){
		int mid=(l+r)>>1;
		seg[x].l=build(l,mid);
		seg[x].r=build(mid+1,r);
	}
	return x;
}
int change(int pre,int l,int r,int d){
	int x=++cnt;
	seg[x].sum=seg[pre].sum+1;
	seg[x].l=seg[pre].l;
	seg[x].r=seg[pre].r;
	if(l==r) return x;
	int mid=(l+r)>>1;
	if(d<=mid) seg[x].l=change(seg[pre].l,l,mid,d);
	else seg[x].r=change(seg[pre].r,mid+1,r,d);
	return x;
}
int query(int u,int v,int l,int r,int d){
	if(l==r) return l;
	int num=seg[v].sum-seg[u].sum;
	if(num<=d) return 0;
	int mid=(l+r)>>1;
	if(seg[seg[v].l].sum-seg[seg[u].l].sum>d){
		return query(seg[u].l,seg[v].l,l,mid,d);
	}else if(seg[seg[v].r].sum-seg[seg[u].r].sum>d){
		return query(seg[u].r,seg[v].r,mid+1,r,d);
	}else return 0;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	rt[0]=build(1,n);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		rt[i]=change(rt[i-1],1,n,a[i]);
	}
	while(q--){
		int x,y,z;
		cin>>x>>y;
		z=(y-x+1)>>1;
		cout<<query(rt[x-1],rt[y],1,n,z)<<"\n"; 
	}
	return 0;
}

[国家集训队]middle

首先可以二分答案,二分判定用到一个 trick:二分一个值为中位数时,将数列中大于它的设为 \(1\),剩下的设为 \(-1\),区间和大于等于 \(0\) 时这个数小于中位数,否则大于中位数。

假设已经完成数列的转换,再对于一个查询进行转换,转换为查询 \([A,B]\) 最大后缀,\([B+1,C-1]\) 区间和与 \([C,D]\) 最大前缀,这显然可以用可持久化线段树解决。

然后我们考虑如何优化效率,我们可以对数组排序,显然对于排序后的数组前面都是 \(-1\) 后面都是 \(1\),逐个数插入即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define debug cout<<"DEBUG"<<endl;
#define pb push_back
#define pii pair<int,int>
#define vi vector<int>
#define imp map<int,int>
using namespace std;
const int N=2e4+5; 
int n,rt[N],q,cnt=0;
int b[6];
struct arr{
	int x,pos;
}a[N];
struct node{
	int l,r,lmax,rmax,sum;
}seg[N<<5];
bool cmp(arr A,arr B){
	return A.x<B.x;
}
void pushup(int x){
	int ls=seg[x].l,rs=seg[x].r;
	seg[x].sum=seg[ls].sum+seg[rs].sum;
	seg[x].lmax=max(seg[ls].lmax,seg[ls].sum+seg[rs].lmax);
	seg[x].rmax=max(seg[rs].rmax,seg[rs].sum+seg[ls].rmax);
}
int build(int l,int r){
	int x=++cnt;
	if(l==r){
		seg[x].lmax=seg[x].rmax=seg[x].sum=1;
		return x;
	}
	int mid=(l+r)>>1;
	seg[x].l=build(l,mid);
	seg[x].r=build(mid+1,r);
	pushup(x);
	return x;
}
int change(int pre,int l,int r,int p,int d){
	int x=++cnt;
	if(l==r){
		seg[x].lmax=seg[x].rmax=seg[x].sum=d;
		return x;
	}
	seg[x].l=seg[pre].l,seg[x].r=seg[pre].r;
	int mid=(l+r)>>1;
	if(p<=mid){
		seg[x].l=change(seg[pre].l,l,mid,p,d);
	}else{
		seg[x].r=change(seg[pre].r,mid+1,r,p,d);
	}
	pushup(x);
	return x;
}
int query_sum(int x,int l,int r,int qx,int qy){
	if(qx<=l&&r<=qy){
		return seg[x].sum;
	}
	int mid=(l+r)>>1;
	if(qy<=mid){
		return query_sum(seg[x].l,l,mid,qx,qy);
	}else if(mid+1<=qx){
		return query_sum(seg[x].r,mid+1,r,qx,qy);
	}else{
		return query_sum(seg[x].l,l,mid,qx,mid)+query_sum(seg[x].r,mid+1,r,mid+1,qy);
	}
} 
int query_l(int x,int l,int r,int qx,int qy){
	if(qx<=l&&r<=qy){
		return seg[x].lmax;
	}
	int mid=(l+r)>>1;
	if(qy<=mid){
		return query_l(seg[x].l,l,mid,qx,qy);
	}else if(mid+1<=qx){
		return query_l(seg[x].r,mid+1,r,qx,qy);
	}else{
		return max(query_l(seg[x].l,l,mid,qx,mid),query_sum(seg[x].l,l,mid,qx,mid)+query_l(seg[x].r,mid+1,r,mid+1,qy));
	}
} 
int query_r(int x,int l,int r,int qx,int qy){
	if(qx<=l&&r<=qy){
		return seg[x].rmax;
	}
	int mid=(l+r)>>1;
	if(qy<=mid){
		return query_r(seg[x].l,l,mid,qx,qy);
	}else if(mid+1<=qx){
		return query_r(seg[x].r,mid+1,r,qx,qy);
	}else{
		return max(query_r(seg[x].r,mid+1,r,mid+1,qy),query_sum(seg[x].r,mid+1,r,mid+1,qy)+query_r(seg[x].l,l,mid,qx,mid));
	}
} 
bool check(int x,int A,int B,int C,int D){
	int sum=0;
	if(B+1<=C-1){
		sum+=query_sum(rt[x],1,n,B+1,C-1);
	}
	sum+=query_r(rt[x],1,n,A,B);
	sum+=query_l(rt[x],1,n,C,D);
	return sum>=0; 
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].x;
		a[i].pos=i;
	} 
	sort(a+1,a+n+1,cmp);
	rt[1]=build(1,n);
	for(int i=2;i<=n+1;i++){
		rt[i]=change(rt[i-1],1,n,a[i-1].pos,-1);
	}
	cin>>q;
	int lst=0;
	while(q--){
		cin>>b[1]>>b[2]>>b[3]>>b[4];
		b[1]=(b[1]+lst)%n;
		b[2]=(b[2]+lst)%n;
		b[3]=(b[3]+lst)%n;
		b[4]=(b[4]+lst)%n;
		sort(b+1,b+5);
		int l=1,r=n;
		while(l<r){
			int mid=(l+r)/2+1;
			if(check(mid,b[1]+1,b[2]+1,b[3]+1,b[4]+1)){
				l=mid;
			}else{
				r=mid-1;
			}
		}
		cout<<a[l].x<<"\n";
		lst=a[l].x;
	}
	return 0;
}
posted @ 2022-08-12 21:40  Aurora_Borealis  阅读(31)  评论(0编辑  收藏  举报