目录 可持续化权值线段树(主席树)各种变体 简单介绍 静态区间第k小 动态区间第k小 可持续化权值线段树(主席树)各种变体 (待更新) 今天终于有机会把暑假留在编辑器中的主席
目录
- 可持续化权值线段树(主席树)各种变体
- 简单介绍
- 静态区间第k小
- 动态区间第k小
可持续化权值线段树(主席树)各种变体
(待更新)
今天终于有机会把暑假留在编辑器中的主席树搬出来晾一晾(雾),图床搭在GitHub上,图片加载速度较慢
简单介绍
博客安利: https://oi-wiki.org/ds/persistent-seg/ , https://blog.csdn.net/bestFy/article/details/78650360
主席树其实是多颗线段树的效果+前缀和的思想,只不过在空间上有了很大的优化,具体优化思路在于每次更新(每次修改对应一颗新的线段树)只会改变二叉树某一条链上的节点,新的线段树可以继承前一颗线段树的没有改变的节点,只为有了更新的节点新建一个节点。
关于空间问题:由于是动态开点,一颗线段树最会出现2*n-1个结点,n次修改,每次最多影响一条链(log n个结点),最坏情况为2*n-1+nlog n,对于1e5的数据,这个值大概是19e5,oi.wiki上表示过于吝啬空间可能会被莫名其妙的卡掉,最好使用(n<<5),空间紧张的情况下至少开20倍空间。
静态区间第k小
代码可过hdu,poj,ACwing和洛谷
#include <bits/stdc++.h> using namespace std; #define fre freopen("data.in","r",stdin); #define frew freopen("sol.out","w",stdout); #define ms(a) memset((a),0,sizeof(a)) #define rep(i, a, b) for(register int i=(a);(i)<(b);++(i)) #define rev(i, a, b) for(register int i=(a);(i)>(b);--(i)) #define erep(i, a, b) for(register int i=(a);(i)<=(b);++(i)) #define erev(i, a, b) for(register int i=(a);(i)>=(b);--(i)) #define all(x) (x).begin(),(x).end() #define bug(x) cout<<x<<endl; #define pb push_back #define lson l,m,i<<1 #define rson m+1,r,i<<1|1 #define reg register #define pc putchar('\n') typedef long long LL; const int inf = (0x7f7f7f7f); inline void sf(int &x) { x = 0; int w = 0; char ch = 0; while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); } while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar(); x = (w ? -x : x); } inline void pf(int x) { if (x < 0) putchar('-'), x = -x; if (x > 9) pf(x / 10); putchar(x % 10 + '0'); } const int maxn =1e5 + 5; int n,m,len; int a[maxn],d[maxn]; //l,r记录该节点左右子树的根节点的下标编号,v记录该区间的值 struct node{int l,r,v;}t[maxn*22]; //T[i]记录第i颗线段树的根节点 int T[maxn]; inline void discretize(){ sort(d+1,d+1+n); len=unique(d+1,d+n+1)-d-1; erep(i,1,n) { a[i] = lower_bound(d + 1, d + 1 + len, a[i]) - d - 1 + 1;//从1开始 //cout<<a[i]<<' '; } } //cnt给每一个节点一个编号,便于通过下标直接访问 int cnt=0; int build(int l,int r){//动态建树,一颗空树 //p为节点下标编号 int p=++cnt,mid=l+r >>1; if(l<r){ t[p].l=build(l,mid); t[p].r=build(mid+1,r); } t[p].v=0; return p; } int update(int pre,int l,int r,int& x){ int p=++cnt,mid= l+r>>1; //继承前一颗线段树的左右子树 t[p].l=t[pre].l,t[p].r=t[pre].r,t[p].v=t[pre].v+1; if(l<r){ //根据更新点的位置决定修改左子树还是右子树 if(x<=mid)t[p].l=update(t[pre].l,l,mid,x); else t[p].r=update(t[pre].r,mid+1,r,x); } return p; } int query(int x,int y,int k,int l,int r){ if(l==r)return l; //sum使用前缀和思想,得到区间线段树 int sum=t[t[y].l].v-t[t[x].l].v,mid=l+r>>1; if(k<=sum)return query(t[x].l,t[y].l,k,l,mid); else return query(t[x].r,t[y].r,k-sum,mid+1,r); } int main() { sf(n);sf(m); erep(i,1,n)sf(a[i]),d[i]=a[i]; discretize(); T[0]=build(1,len);//新建一颗空树 erep(i,1,n)T[i]=update(T[i-1],1,len,a[i]);//每次修改对应一颗新的线段树 while(m--){ int l,r,k; sf(l);sf(r);sf(k); printf("%d\n",d[query(T[l-1],T[r],k,1,len)]);//记得离散回原来的数 } return 0; }