可持久化线段树学习笔记
可持久化线段树,即保存每一次修改后的版本的线段树。
首先考虑最朴素的操作:对于每次修改,将前一棵线段树复制一遍,然后在新树上进行修改。
显然这样做复制树的时间十分劣,而且空间也会爆炸。考虑优化这一过程。
容易发现,线段树上每次修改操作只会更新一条链上的值,其他点不变,所以我们复制无关的点浪费了大量时间。可以只新建修改的这条链,对于链上一个点,新建链上的下一个点,另一个儿子直接连接到上一棵树的相同位置(所以这里不能用普通线段树的乘二表示法了,只能用指针式线段树)。
这样就完成了可持久化线段树的构造。
以板子题举例:
先不考虑这个问题,先考虑一个弱化版:求 \([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;
}
例题:
跟上一题类似,只不过是求区间众数。
同样建立可持久化权值线段树,对于一个点看两边的点是否大于查询区间的长度一半,如果大于的话说明有可能存在众数。
值得一提的是这题的良心出题人为我们省去了离散化。这种人现在已经灭绝了
点击查看代码
#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;
}
首先可以二分答案,二分判定用到一个 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;
}
本文来自博客园,作者:Aurora_Borealis,转载请注明原文链接:https://www.cnblogs.com/Aurora-Borealis-Not-Found/p/16581463.html