主席树(可持久化线段树)

mac2022-06-30  21

经典问题:求全局第K大

思路:可以在权值线段树上二分,当左儿子存储的个数大于k时在左儿子寻找,否则将k减去左儿子存储的个数在右儿子寻找

主席树经典题:求区间第k大

如果我们像全局第K大一样给每个区间建一个线段树是不可能的,考虑做一个前缀和,用第R个线段树减去第L-1个线段树就是[L,R]的线段树,再做全局第K大即可

但是仍然会MLE,事实上不需要每个数建一棵线段树,每次加入只需要插入的路径上更新即可,所以每次的空间复杂度是O(logn)

这样做实际上可以回到任何一个历史状态,这就是所谓的可持久化线段树——主席树

 

需要注意的是,因为要更新路径,我们按照遍历的顺序每到一个新节点编号加一(不同于传统线段树左儿子是P>>1右儿子是P>>1|1的编号方式)

 数组注释:lc[i]表示i的左儿子,rc[i]表示i的右儿子,rt[i]表示第i次更改时的根编号

build函数

void build(int &t,int l,int r) { t=++cnt;//每访问一个新的节点就编号加一 if(l==r) return; int mid=(l+r)>>1; build(lc[t],l,mid); build(rc[t],mid+1,r); }

比较简单,需要注意是按遍历顺序开点

modify函数(insert)

int modify(int o,int l,int r)//o表示上一个状态的编号 { int h=++cnt; lc[h]=lc[o]; rc[h]=rc[o];/*左右儿子都复制一下*/ sums[h]=sums[o]+1;//当前节点处于插入路径上,需要更新 if(l==r) return h; int mid=(l+r)>>1; if(place<=mid) lc[h]=modify(lc[h],l,mid);//将lc[h]更新,成链状 else rc[h]=modify(rc[h],mid+1,r); return h; }

比较重要的函数,可以结合图片理解

以下是插入数字4时的情况

query函数

//u是l-1时的线段树,v是r时的线段树 //query(rt[l-1],rt[r],1,num,k) int query(int u,int v,int l,int r,int k) { int ans,mid=(l+r)>>1; int x=sums[lc[v]]-sums[lc[u]];//相当于[l,r]线段树lc[x]所存的个数 if(l==r) return l;//找到了第k小 if(x>=k) ans=query(lc[u],lc[v],l,mid,k); else ans=query(rc[u],rc[v],mid+1,r,k-x); return ans; //别忘了return 回去 }

前缀和的思想,前缀减前缀,区间变全局

code

#include<cstdio> #include<iostream> #include<algorithm> #define N 500005 using namespace std; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int a[N],index[N],rt[N],lc[N<<3],b[N]; int l,r,k,ans,num,place,n,m; int cnt,sums[N<<3],rc[N<<3]; void build(int &t,int l,int r) { t=++cnt;//每访问一个新的节点就编号加一 if(l==r) return; int mid=(l+r)>>1; build(lc[t],l,mid); build(rc[t],mid+1,r); } int modify(int o,int l,int r)//o表示上一个状态的编号 { int h=++cnt; lc[h]=lc[o]; rc[h]=rc[o];/*左右儿子都复制一下*/ sums[h]=sums[o]+1;//当前节点处于插入路径上,需要更新 if(l==r) return h; int mid=(l+r)>>1; if(place<=mid) lc[h]=modify(lc[h],l,mid);//将lc[h]更新,成链状 else rc[h]=modify(rc[h],mid+1,r); return h; } //u是l-1时的线段树,v是r时的线段树 //query(rt[l-1],rt[r],1,num,k) int query(int u,int v,int l,int r,int k) { int ans,mid=(l+r)>>1; int x=sums[lc[v]]-sums[lc[u]];//相当于[l,r]线段树lc[x]所存的个数 if(l==r) return l;//找到了第k小 if(x>=k) ans=query(lc[u],lc[v],l,mid,k); else ans=query(rc[u],rc[v],mid+1,r,k-x); return ans; //别忘了return 回去 } int main() { n=read(); m=read(); for(register int i=1;i<=n;++i){a[i]=read();b[i]=a[i];} sort(b+1,b+n+1); num=unique(b+1,b+n+1)-b-1;//离散化 build(rt[0],1,num); for(register int i=1;i<=n;i++) { place=lower_bound(b+1,b+num+1,a[i])-b; rt[i]=modify(rt[i-1],1,num); } while(m--) { l=read(); r=read(); k=read(); ans=query(rt[l-1],rt[r],1,num,k); printf("%d\n",b[ans]); } return 0; }

 

转载于:https://www.cnblogs.com/Liuz8848/p/10454464.html

最新回复(0)