因为我真的是太弱了,因此真的是浅谈。node
神奇的数据结构其实我也很虚啊!git
简单的说,值域线段树区间里面存的是在这个区间内的数的个数有多少个。数组
有没有感受很简单,考虑一下若是咱们有一棵这样的线段树,查找排名为rk的数时只须要看一下左子树的大小就能够判断在左边仍是右边了。数据结构
有没有感受很像BSTide
根据上面的值域线段树,咱们能够得出一种naive的作法:优化
对于每个前缀\([1,i](i\in[1,n])\)都开一棵值域线段树,而后查询区间的时候直接每一个节点的size都变成\(size(r)-size(l-1)\),就是把前缀和的思想搬到了树上ui
而后不只要MLE,每次复制线段树都T了spa
咱们考虑优化一波,咱们发现每一次操做只会改变从根节点到叶子节点的一条路径上点的值。code
而后因为线段树的深度是稳定且均匀的\(logn\),所以咱们发现许多节点的信息都是能够共用的:blog
咱们在修改红色的这一条路径的信息时,咱们把这一条链上的点都新建一个分身并和原图保持同样的形状。
咱们在每一次操做后都记录当前的根节点的编号便可。这就是传说中的可持久化,由于咱们成功地保存了历史版本。
固然,这样的线段树不可能保持固定的形态,所以咱们记录每个点的左右儿子的信息。而后每次更新时再开新的节点,这就是动态开点(平衡树写多的话就彻底没问题)
看板子题Luogu P3834 【模板】可持久化线段树 1(主席树)
首先值域线段树不能处理那么大的数字,所以咱们先离散化
而后大致的作法上面都讲了,直接上CODE吧
#include<cstdio> #include<cctype> #include<algorithm> using namespace std; const int N=2e5+5; struct President_tree//President总统,不知道有没有主席的意思 { int lc,rc,sum; }node[N<<6]; int n,m,q,a[N],b[N],l,r,k,rt[N],tot; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1; while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag; } inline void write(int x) { if (x>9) write(x/10); putchar(x%10+'0'); } inline int find(int x) { int l=1,r=m; while (l<=r) { int mid=l+r>>1; if (b[mid]==x) return mid; if (b[mid]<x) l=mid+1; else r=mid-1; } } inline void build(int &now,int l,int r)//建树,注意动态开点 { now=++tot; if (l==r) return; int mid=l+r>>1; build(node[now].lc,l,mid); build(node[now].rc,mid+1,r); } inline int modify(int lst,int l,int r,int id)//更新某段路径的值 { int now=++tot; node[now]=node[lst]; ++node[now].sum; if (l==r) return now; int mid=l+r>>1; if (id<=mid) node[now].lc=modify(node[lst].lc,l,mid,id); else node[now].rc=modify(node[lst].rc,mid+1,r,id); return now; } inline int query(int u,int v,int l,int r,int k)//查询区间第k小,这里直接同时跳两条路径了 { int mid=l+r>>1,dlt=node[node[v].lc].sum-node[node[u].lc].sum; if (l==r) return l; if (dlt>=k) return query(node[u].lc,node[v].lc,l,mid,k); else return query(node[u].rc,node[v].rc,mid+1,r,k-dlt); } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); read(q); for (i=1;i<=n;++i) read(a[i]),b[i]=a[i]; sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; build(rt[0],1,m); for (i=1;i<=n;++i) rt[i]=modify(rt[i-1],1,m,find(a[i])); for (i=1;i<=q;++i) { read(l); read(r); read(k); write(b[query(rt[l-1],rt[r],1,m,k)]); putchar('\n'); } return 0; }
板子题:P3919 【模板】可持久化数组(可持久化线段树/平衡树)
这个就更加简单了,咱们至关于维护一个可持久化线段树,并支持单点修改,单点查询便可
是否是听起来很ZZ,感受彻底不须要线段树
不过线段树起到了良好的存储信息+快速查找的左右,所以是不二选择(固然你要非旋Treap我也不想管你)
CODE
#include<cstdio> #include<cctype> #include<algorithm> using namespace std; const int N=1e6+5; struct Persistence_segtree//Persistence-这个是可持久化的意思 { int lc,rc,x; }node[N*20]; int n,m,a[N],rt[N],opt,x,y,id,tot; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1; while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag; } inline void write(int x) { if (x<0) putchar('-'),x=-x; if (x>9) write(x/10); putchar(x%10+'0'); } inline void build(int &now,int l,int r) { now=++tot; if (l==r) { node[now].x=a[l]; return; } int mid=l+r>>1; build(node[now].lc,l,mid); build(node[now].rc,mid+1,r); } inline int modify(int lst,int l,int r,int id,int x) { int now=++tot; node[now]=node[lst]; if (l==r) { node[now].x=x; return now; } int mid=l+r>>1; if (id<=mid) node[now].lc=modify(node[lst].lc,l,mid,id,x); else node[now].rc=modify(node[lst].rc,mid+1,r,id,x); return now; } inline int query(int now,int l,int r,int id) { if (l==r) return node[now].x; int mid=l+r>>1; if (id<=mid) return query(node[now].lc,l,mid,id); else return query(node[now].rc,mid+1,r,id); } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); read(m); for (i=1;i<=n;++i) read(a[i]); build(rt[0],1,n); for (i=1;i<=m;++i) { read(x); read(opt); read(id); if (opt^2) read(y),rt[i]=modify(rt[x],1,n,id,y); else write(query(rt[i]=rt[x],1,n,id)),putchar('\n'); } return 0; }
这个是最烦人的操做了吧,理论上只须要可持久化数组就能够搞了。
首先咱们要摒弃掉平时的那些关于并查集的想法,在可持久化并查集中,不能够路径压缩。
那么还不GG,直接搞不是爆炸?
其实还有一个优化叫作按秩合并,也是启发式合并的一种,就是记录一下每一个联通块的大小,而后每次合并的时候把小的并到大的上面去便可
复杂度最差\(O(n\ log n)\) ,彻底不虚的好很差。
CODE
#include<cstdio> #include<cctype> using namespace std; const int N=200005; struct President_tree { int ch[2],fa,dep; }node[N*20]; int n,m,opt,x,y,rt[N],tot; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; while (!isdigit(ch=tc())); while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); } inline void swap(int &a,int &b) { int t=a; a=b; b=t; } inline void build(int &now,int l,int r) { now=++tot; if (l==r) { node[now].fa=l; return; } int mid=l+r>>1; build(node[now].ch[0],l,mid); build(node[now].ch[1],mid+1,r); } inline void insert(int lst,int &now,int l,int r,int x,int fa) { now=++tot; if (l==r) { node[now].fa=fa; node[now].dep=node[lst].dep; return; } int mid=l+r>>1; node[now].ch[0]=node[lst].ch[0]; node[now].ch[1]=node[lst].ch[1]; if (x<=mid) insert(node[lst].ch[0],node[now].ch[0],l,mid,x,fa); else insert(node[lst].ch[1],node[now].ch[1],mid+1,r,x,fa); } inline void updata(int now,int l,int r,int x) { if (l==r) { ++node[now].dep; return; } int mid=l+r>>1; if (x<=mid) updata(node[now].ch[0],l,mid,x); else updata(node[now].ch[1],mid+1,r,x); } inline int query(int now,int l,int r,int x) { if (l==r) return now; int mid=l+r>>1; if (x<=mid) return query(node[now].ch[0],l,mid,x); else return query(node[now].ch[1],mid+1,r,x); } inline int getfather(int t,int x) { int fa=query(t,1,n,x); return x^node[fa].fa?getfather(t,node[fa].fa):fa; } inline void unionn(int t,int x,int y) { rt[t]=rt[t-1]; int fx=getfather(rt[t],x),fy=getfather(rt[t],y); if (node[fx].fa==node[fy].fa) return; if (node[fx].dep<node[fy].dep) swap(fx,fy); insert(rt[t-1],rt[t],1,n,node[fy].fa,node[fx].fa); if (node[fx].dep==node[fy].dep) updata(rt[t],1,n,node[fx].fa); } inline void check(int t,int x,int y) { rt[t]=rt[t-1]; int fx=getfather(rt[t],x),fy=getfather(rt[t],y); puts(node[fx].fa^node[fy].fa?"0":"1"); } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); read(m); build(rt[0],1,n); for (i=1;i<=m;++i) { read(opt); read(x); switch (opt) { case 1:read(y),unionn(i,x,y);break; case 2:rt[i]=rt[x];break; case 3:read(y),check(i,x,y);break; } } return 0; }
PS:刚开始swap写错了害我调了2H+