可持久化线段树
可持久化线段树,又称主席树,通常用于维护序列和值域。
对于单点修改的可持久化线段树,它主要的思想就是每次新建一个版本,复制一条链。
单点修改和查询历史版本。不能再用和记录根节点的左右子树,需要用变量来记录,每次修改最多复制的链。
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小。对于序列的每个节点都用一个权值线段树维护,记录]内每个数字出现的次数,也就是前缀和,在查询时前缀和可以相减,于是将区间内值域逐渐缩小即可。
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; }
求区间。
一段区间的是由区间乘积再除上区间两两构成的,将每个数质因数分解,对于质因数的次幂,可以找到上一个所在上一个位置,在该位置乘的逆元,这样就除掉了两个数的,注意初始化可持久化线段树根节点的权值要是,即.
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'; }
从区间内取出一个长度为k的区间,求区间最小值的最大值。
考虑二分区间最小值,将的视作,的视作,可以转化为区间内是否存在一个长度不小于的连续的段,离散化后用可持久化线段树,从大到小依次插入数字所处的位置。
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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】