可持久化线段树

可持久化线段树,又称主席树,通常用于维护序列和值域。

对于单点修改的可持久化线段树,它主要的思想就是每次新建一个版本,复制一条链。

单点修改和查询历史版本。不能再用lcrc记录根节点的左右子树,需要用变量来记录,每次修改最多复制O(logN)的链。

struct PersistentSegmentTree{
struct tree{
int l,r,v;
}t[N<<5];
int tot,root[N];/*历史版本*/
#define l(p) (t[p].l)
#define r(p) (t[p].r)
#define v(p) (t[p].v)
void build(int&p,int l,int r,int a[]){
p=++tot;/*新建节点*/
if(l==r){
v(p)=a[l];/*赋值*/
return;
}
int mid=l+r>>1;
build(l(p),l,mid,a);
build(r(p),mid+1,r,a);
}
void insert(int&p,int q,int l,int r,int x,int v){
p=++tot;
t[p]=t[q];/*复制这条链*/
if(l==r){
v(p)=v;/*单点修改*/
return;
}
int mid=l+r>>1;
if(x<=mid)insert(l(p),l(q),l,mid,x,v);
else insert(r(p),r(q),mid+1,r,x,v);
}
int query(int p,int l,int r,int x){
if(l==r)return v(p);
int mid=l+r>>1;
if(x<=mid)return query(l(p),l,mid,x);
else return query(r(p),mid+1,r,x);
}
inline void modify(int x,int y){
root[x]=root[y];
}
}pst;

静态区间第k小。对于序列的每个节点都用一个权值线段树维护,记录[1,i]内每个数字出现的次数,也就是前缀和,在查询时前缀和可以相减,于是将区间内值域逐渐缩小即可。

struct PersistentSegmentTree{
struct tree{
int l,r,sum;
}t[N<<5];
int tot,root[N];
#define l(p) (t[p].l)
#define r(p) (t[p].r)
#define s(p) (t[p].sum)
inline void pushup(int p){
s(p)=s(l(p))+s(r(p));/*前缀和*/
}
void build(int&p,int l,int r){
p=++tot;
if(l==r)return;
int mid=l+r>>1;
build(l(p),l,mid);
build(r(p),mid+1,r);
}
void insert(int&p,int q,int l,int r,int x){
p=++tot;
t[p]=t[q];
s(p)++;/*更新前缀和*/
if(l==r)return;
int mid=l+r>>1;
if(x<=mid)insert(l(p),l(q),l,mid,x);
else insert(r(p),r(q),mid+1,r,x);
pushup(p);
}
int kth(int p,int q,int l,int r,int k,int a[]){
if(l==r)return a[l];/*找到最后的一个点*/
int mid=l+r>>1,x=s(l(p))-s(l(q));/*值域范围内的数出现的次数*/
if(k<=x)return kth(l(p),l(q),l,mid,k,a);/*当前值域出现次数>=k,则一定在左子树*/
else return kth(r(p),r(q),mid+1,r,k-x,a);/*递归右子树时减去当前值域出现次数*/
}
}pst;

动态区间主席树。树状数组套权值线段树,每个点的修改会影响当前版本的根到最后一个版本的根,单点修改区间查询,可以使用树状数组。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int a[N],n,m;
struct PersistentSegmentTree{
struct tree{
int l,r,sum;
}t[N<<8];
int root[N],tot,rt[2][33];
#define l(p) (t[p].l)
#define r(p) (t[p].r)
#define s(p) (t[p].sum)
inline int lowbit(int x){
return x&-x;
}
void insert(int&p,int l,int r,int x,int v){
if(!p)p=++tot;
s(p)+=v;
if(l==r)return;
int mid=l+r>>1;
if(x<=mid)insert(l(p),l,mid,x,v);
else insert(r(p),mid+1,r,x,v);
}
inline int kth(int p,int q,int k){
int t1=0,t2=0,l=0,r=1e9;
for(int i=p;i;i-=lowbit(i))rt[0][++t1]=root[i];
for(int i=q;i;i-=lowbit(i))rt[1][++t2]=root[i];
while(l<r){
int re=0,mid=l+r>>1;
for(int i=1;i<=t1;i++)re+=s(l(rt[0][i]));/*累加根在[p,n]的主席树左子树的和*/
for(int i=1;i<=t2;i++)re-=s(l(rt[1][i]));/*差分根在[q,n]的主席树左子树的和*/
if(k<=re){/*跳到左子树*/
for(int i=1;i<=t1;i++)rt[0][i]=l(rt[0][i]);/*所有根都要跳过去*/
for(int i=1;i<=t2;i++)rt[1][i]=l(rt[1][i]);
r=mid;
}
else{/*跳到右子树*/
for(int i=1;i<=t1;i++)rt[0][i]=r(rt[0][i]);/*所有根都要跳过去*/
for(int i=1;i<=t2;i++)rt[1][i]=r(rt[1][i]);
k-=re;/*减去左子树的大小*/
l=mid+1;
}
}
return l;
}
inline void modify(int x,int v){
for(int i=x;i<=n;i+=lowbit(i))insert(root[i],0,1e9,a[x],-1);
a[x]=v;
for(int i=x;i<=n;i+=lowbit(i))insert(root[i],0,1e9,a[x],1);
}
}seg;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=i;j<=n;j+=j&-j)seg.insert(seg.root[j],0,1e9,a[i],1);
}
while(m--){
string s;
int x,y,k;
cin>>s>>x>>y;
if(s=="Q")cin>>k,cout<<seg.kth(y,x-1,k)<<'\n';
else seg.modify(x,y);
}
return 0;
}

求区间lcm

一段区间的lcm是由区间乘积再除上区间两两gcd构成的,将每个数质因数分解,对于质因数px次幂,可以找到上一个x所在上一个位置,在该位置乘x的逆元,这样就除掉了两个数的gcd,注意初始化可持久化线段树根节点的权值要是1,即t[0].v=1.

struct PersistSegmentTree{
struct tree{
int l,r,v;
}t[N*400];
int tot,root[N];
#define l(p) (t[p].l)
#define r(p) (t[p].r)
#define v(p) (t[p].v)
inline void pushup(int p){
v(p)=1ll*v(l(p))*v(r(p))%mod;
}
void insert(int&p,int q,int l,int r,int x,int v){
p=++tot;
t[p]=t[q];
if(l==r)return v(p)=1ll*v*v(p)%mod,void();
int mid=l+r>>1;
if(x<=mid)insert(l(p),l(q),l,mid,x,v);
else insert(r(p),r(q),mid+1,r,x,v);
pushup(p);
}
int query(int p,int l,int r,int x){
if(l>=x)return v(p);
if(r<x)return 1;
int mid=l+r>>1;
return 1ll*query(l(p),l,mid,x)*query(r(p),mid+1,r,x)%mod;
}
}ps;
for(int i=1;i<=n;i++){
int x;
cin>>x;
ps.root[i]=ps.root[i-1];
while(miv[x]){
int k=miv[x],t=1;
while(x%k==0){
t*=k;
x/=k;
if(pre[t])ps.insert(ps.root[i],ps.root[i],1,n,pre[t],inv[k]);
pre[t]=i;
}
ps.insert(ps.root[i],ps.root[i],1,n,i,t);
}
}
cin>>q;
while(q--){
int l,r;
cin>>l>>r;
l=(l+ans)%n+1,r=(r+ans)%n+1;
if(l>r)swap(l,r);
cout<<(ans=ps.query(ps.root[r],1,n,l))<<'\n';
}

从区间[l,r]内取出一个长度为k的区间,求区间最小值的最大值。

考虑二分区间最小值,将>=mid的视作1<mid的视作0,可以转化为区间内是否存在一个长度不小于k的连续的1段,离散化后用可持久化线段树,从大到小依次插入数字所处的位置。

struct PersistSegmentTree{
struct tree{
int len,lma,rma,tma;
inline tree(){len=lma=rma=tma=0;}
inline tree(int len,int lma,int rma,int tma):len(len),lma(lma),rma(rma),tma(tma){}
inline tree operator+(const tree&rhs){
return (tree){len+rhs.len,lma==len?lma+rhs.lma:lma,rhs.rma==rhs.len?rhs.rma+rma:rhs.rma,max({tma,rhs.tma,rma+rhs.lma})};
}
}t[N<<5];
int tot,root[N],lc[N<<5],rc[N<<5];
#define l(p) (lc[p])
#define r(p) (rc[p])
#define le(p) (t[p].len)
#define lm(p) (t[p].lma)
#define rm(p) (t[p].rma)
#define t(p) (t[p].tma)
void build(int&p,int l,int r){
p=++tot;
if(l==r)return le(p)=1,void();
int mid=l+r>>1;
build(l(p),l,mid);
build(r(p),mid+1,r);
t[p]=t[l(p)]+t[r(p)];
}
void insert(int&p,int q,int l,int r,int x){
p=++tot;
l(p)=l(q);
r(p)=r(q);
t[p]=t[q];
if(l==r){
le(p)=lm(p)=rm(p)=t(p)=1;
return;
}
int mid=l+r>>1;
if(x<=mid)insert(l(p),l(q),l,mid,x);
else insert(r(p),r(q),mid+1,r,x);
t[p]=t[l(p)]+t[r(p)];
}
tree query(int p,int l,int r,int x,int y){
if(x<=l&&r<=y)return t[p];
int mid=l+r>>1;
tree re;
if(x<=mid)re=re+query(l(p),l,mid,x,y);
if(mid<y)re=re+query(r(p),mid+1,r,x,y);
return re;
}
inline int query(int x,int y,int k){
int l=1,r=n,re=0;
while(l<=r){
int mid=l+r>>1;
if(query(root[mid],1,n,x,y).tma>=k)r=mid-1,re=mid;
else l=mid+1;
}
return re;
}
}pst;
struct node{
int val,id;
inline friend bool operator<(const node&a,const node&b){return a.val>b.val;}
}a[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].val,a[i].id=i;
sort(a+1,a+1+n);
pst.build(pst.root[0],1,n);
for(int i=1;i<=n;i++)pst.insert(pst.root[i],pst.root[i-1],1,n,a[i].id),cout<<a[i].val<<' '<<a[i].id<<'\n';
int m;
cin>>m;
while(m--){
int l,r,k;
cin>>l>>r>>k;
cout<<a[pst.query(l,r,k)].val<<'\n';
}
return 0;
}
posted @   半步蒟蒻  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示