笔者近几天研究了一下早已久仰的主席树!(据说可是主席发明的树哦)
看着讲义和网上的博客yy了好久,最后还是看程序看明白的,衰。。。。。。
极力推荐CLJ的《可持久化数据结构研究》,写的非常好,虽然蒟蒻一开始没看懂。。。。
--------------------------------------------------华丽的分割线--------------------------------------------------
从k大数开始说起吧。
简单点说,主席树就是 建了n个权值线段树 ,这样每次询问区间L,R的第k大数,我们很容易得到sum_R[ p ]-sum_L-1[ p ]的值,即在L-R中权值在lef[p]-rig[p]中的数有多少个,递归下去我们就能知道求出k小数了。
但是这样的做法空间是O(n^2),MLE。。。。囧
这个解决的办法也就是主席真正神奇的地方了!仔细观察 树i 和 树i+1,没错,这两棵树只相差一条路径,也就是logn个点!也就是说我们完全没有必要把每棵树上的点新建起来,完全可以利用之前已有的信息。按这种方法空间问题就完美的得到解决了。空间复杂度O(nlogn)。
例题 POJ2104
裸的静态k大数,主席树水过吧。。。。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int Maxn=5000005; int a[Maxn],b[Maxn],n,m,t,i,l,r,k; struct node { int lef, rig, sum; node *lc, *rc; }; node *T[Maxn], memory[Maxn]; node *start = memory; node *build(int l,int r){ node *p=start++; p->lef = l; p->rig = r; p->sum = 0; if (l>=r) return p; int mid=(l+r)>>1; p->lc = build(l,mid); p->rc = build(mid+1,r); return p; } node *insert(node *p,int data){ node *q=start++; *q=*p; q->sum++; if (p->lef<p->rig){ if (data<=(p->lef+p->rig)/2) q->lc=insert(q->lc,data); else q->rc=insert(q->rc,data); } return q; } int query(node *p,node *q,int k){ if (p->lef==p->rig) return p->lef; int tmp=q->lc->sum - p->lc->sum; if (k<=tmp) return query(p->lc,q->lc,k); else return query(p->rc,q->rc,k-tmp); } void work(){ T[0]=build(1,t); for (i=1;i<=n;i++) T[i]=insert(T[i-1],a[i]); for (i=1;i<=m;i++){ scanf("%d%d%d",&l,&r,&k); printf("%d\n",b[query(T[l-1],T[r],k)]); } } int main(){ scanf("%d%d",&n,&m); for (i=1;i<=n;i++){ scanf("%d",&a[i]); b[++t]=a[i]; } sort(b+1,b+t+1); t=unique(b+1,b+t+1)-b-1; for (i=1;i<=n;i++) a[i]=lower_bound(b+1,b+t+1,a[i])-b; work(); return 0; }
--------------------------------------------------华丽的分割线--------------------------------------------------
带修改的第k小数
我们不能再像之前那样第i棵树表示1-i的权值线段树了。虽然这样询问复杂度还是O(logn),但是修改的复杂度高达O(nlogn)
怎么办?看来我的得找到一个折中的方法,使得询问修改复杂度都不太高。
“第i棵树表示1-i的权值线段树”,其实这是本质是前缀和。前缀和是不好维护的,但是我们想到了“半前缀和”:树状数组!
对了,在最外层套上树状数组,树状数组的每个节点上记得是i-lowbit(i)—i的权值线段树,这样我们虽然空间复杂度上多了个log,但是询问和修改复杂度平衡到了O(log^2n)
例题 bzoj1901
#include <vector> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define lowbit(x) ((x)&(-(x))) const int Maxn=20005; int n,m,i,j,k,t1,t2,t,start; int L[Maxn],R[Maxn],a[Maxn],b[Maxn],T[Maxn]; struct arr { int l,r,k; } Q[Maxn]; struct node { int sum, lc, rc; } tr[3000005]; void insert(int p,int &q,int l,int r,int x,int k){ q=++start; tr[q]=tr[p]; tr[q].sum+=k; if (l<r){ int mid=(l+r)>>1; if (x<=mid) insert(tr[q].lc,tr[q].lc,l,mid,x,k); else insert(tr[q].rc,tr[q].rc,mid+1,r,x,k); } } int query(int l,int r,int x){ if (l==r) return l; int tmp=0, mid=(l+r)>>1; for (int i=1;i<=t1;i++) tmp-=tr[ tr[ L[i] ].lc ].sum; for (int i=1;i<=t2;i++) tmp+=tr[ tr[ R[i] ].lc ].sum; if (tmp>=x){ for (int i=1;i<=t1;i++) L[i]=tr[ L[i] ].lc; for (int i=1;i<=t2;i++) R[i]=tr[ R[i] ].lc; query(l,mid,x); } else { for (int i=1;i<=t1;i++) L[i]=tr[ L[i] ].rc; for (int i=1;i<=t2;i++) R[i]=tr[ R[i] ].rc; query(mid+1,r,x-tmp); } } int main(){ //freopen("1901.in","r",stdin); //freopen("1901.out","w",stdout); scanf("%d%d",&n,&m); for (i=1;i<=n;i++){ scanf("%d",&a[i]); b[++t]=a[i]; } char s[3]; for (i=1;i<=m;i++){ scanf("%s",s); if (s[0]=='Q') scanf("%d%d%d\n",&Q[i].l,&Q[i].r,&Q[i].k); else{ scanf("%d%d\n",&Q[i].l,&Q[i].k); b[++t]=Q[i].k; } } sort(b+1,b+t+1); t=unique(b+1,b+t+1)-b-1; for (i=1;i<=n;i++){ a[i]=lower_bound(b+1,b+t+1,a[i])-b; for (j=i;j<=n;j+=lowbit(j)) insert(T[j],T[j],1,t,a[i],1); } for (i=1;i<=m;i++){ if (Q[i].r==0){ for (j=Q[i].l;j<=n;j+=lowbit(j)) insert(T[j],T[j],1,t,a[Q[i].l],-1); a[Q[i].l]=lower_bound(b+1,b+t+1,Q[i].k)-b; for (j=Q[i].l;j<=n;j+=lowbit(j)) insert(T[j],T[j],1,t,a[Q[i].l],1); } else { t1=t2=0; for (j=Q[i].l-1;j>0;j-=lowbit(j)) L[++t1]=T[j]; for (j=Q[i].r;j>0;j-=lowbit(j)) R[++t2]=T[j]; printf("%d\n",b[query(1,t,Q[i].k)]); } } return 0; }
--------------------------------------------------华丽的分割线--------------------------------------------------
树上也可以建主席树!?
假如我们询问树上两点路径上的第k小值,怎么办???
简单啊,每个点上记下起到根的路径的权值线段树。询问x,y时找到其最近公共祖先z和z的父节点zz。
把之前的式子改进成 sum[x]+sum[y]-sum[z]-sum[zz] 即可!
例题
1、bzoj2588
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int Maxn=200005; int node[Maxn],next[Maxn],a[Maxn],b[Maxn],dt[Maxn],dep[Maxn]; int start,n,m,i,x,y,k,t,tot,l,r,z,zz,fa[Maxn][20],T[Maxn],q[Maxn]; struct arr { int lc,rc,sum; } tr[2200005]; void add(int x,int y){ node[++tot]=y; next[tot]=a[x]; a[x]=tot; node[++tot]=x; next[tot]=a[y]; a[y]=tot; } void insert(int p,int &q,int l,int r,int x){ q=++start; tr[q]=tr[p]; tr[q].sum++; if (l<r){ int mid=(l+r)>>1; if (x<=mid) insert(tr[q].lc,tr[q].lc,l,mid,x); else insert(tr[q].rc,tr[q].rc,mid+1,r,x); } } int query(int p1,int p2,int p3,int p4,int l,int r,int x){ if (l==r) return l; int tmp=tr[ tr[p1].lc ].sum+tr[ tr[p2].lc ].sum-tr[ tr[p3].lc ].sum-tr[ tr[p4].lc ].sum; int mid=(l+r)>>1; if (tmp>=x) return query(tr[p1].lc,tr[p2].lc,tr[p3].lc,tr[p4].lc,l,mid,x); else return query(tr[p1].rc,tr[p2].rc,tr[p3].rc,tr[p4].rc,mid+1,r,x-tmp); } int LCA(int x,int y){ if (dep[x]<dep[y]) swap(x,y); for (int i=16;i>=0;i--) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i]; for (int i=16;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i]; if (x==y) return x; return fa[x][0]; } void init(){ dep[1]=1; for (q[l=r=1]=1;l<=r;l++) for (i=a[q[l]];i;i=next[i]) if (node[i]!=fa[q[l]][0]){ fa[ q[++r]=node[i] ][0]=q[l]; dep[q[r]]=dep[q[l]]+1; } for (int j=1;j<17;j++) for (i=1;i<=n;i++) fa[i][j]=fa[ fa[i][j-1] ][j-1]; for (i=1;i<=n;i++) insert(T[ fa[q[i]][0] ],T[q[i]],1,t,dt[q[i]]); } int main(){ //freopen("cot.in","r",stdin); //freopen("cot.out","w",stdout); scanf("%d%d",&n,&m); for (i=1;i<=n;i++){ scanf("%d",&dt[i]); b[i]=dt[i]; } sort(b+1,b+n+1); t=unique(b+1,b+n+1)-b-1; for (i=1;i<=n;i++) dt[i]=lower_bound(b+1,b+t+1,dt[i])-b; for (i=1;i<n;i++){ scanf("%d%d",&x,&y); add(x,y); } init(); int last=0; for (i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&k); x^=last; z=LCA(x,y); zz=fa[z][0]; last=b[ query(T[x],T[y],T[z],T[zz],1,t,k) ]; if (i<m) printf("%d\n",last); else printf("%d",last); } return 0; }
注意一下Link操作时用启发式合并即可
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int Maxn=80005; int node[Maxn*4],next[Maxn*4],a[Maxn],q[Maxn],dt[Maxn],b[Maxn],dep[Maxn]; int fa[Maxn][20],size[Maxn],T[Maxn],n,m,t,N,i,j,k,tot,x,y,start,l,r,z,zz,fx,fy; struct arr { int sum, lc, rc; } tr[25000005]; void add(int x,int y){ node[++tot]=y; next[tot]=a[x]; a[x]=tot; node[++tot]=x; next[tot]=a[y]; a[y]=tot; } void insert(int p,int &q,int l,int r,int x){ q=++start; tr[q]=tr[p]; tr[q].sum++; if (l<r){ int mid=(l+r)>>1; if (x<=mid) insert(tr[p].lc,tr[q].lc,l,mid,x); else insert(tr[p].rc,tr[q].rc,mid+1,r,x); } } int query(int p1,int p2,int p3,int p4,int k){ int l=1, r=N, tmp, mid; while (l<r){ tmp = tr[ tr[p1].lc ].sum + tr[ tr[p2].lc ].sum - tr[ tr[p3].lc ].sum - tr[ tr[p4].lc ].sum; mid = (l+r)>>1; if (tmp>=k) p1=tr[p1].lc, p2=tr[p2].lc, p3=tr[p3].lc, p4=tr[p4].lc, r=mid; else p1=tr[p1].rc, p2=tr[p2].rc, p3=tr[p3].rc, p4=tr[p4].rc, l=mid+1, k-=tmp; } return l; } void bfs(){ for (i=1,l=1;i<=n;i++) if (fa[i][0]==0) for (q[++r]=i, dep[i]=1;l<=r;l++) for (j=a[q[l]];j;j=next[j]) if (node[j]!=fa[q[l]][0]){ fa[ q[++r]=node[j] ][0] = q[l]; dep[ node[j] ] = dep[ q[l] ]+1; } for (i=n;i>0;i--){ size[q[i]]++; if (fa[q[i]][0]!=0) size[fa[q[i]][0]]+=size[q[i]]; } for (j=1;j<17;j++) for (i=1;i<=n;i++) fa[i][j]=fa[ fa[i][j-1] ][j-1]; for (i=1;i<=n;i++) insert(T[fa[q[i]][0]],T[q[i]],1,N,dt[q[i]]); } int gf(int x){ for (int i=16;i>=0;i--) if (fa[x][i]!=0) x=fa[x][i]; return x; } int LCA(int x,int y){ if (dep[x]<dep[y]) swap(x,y); for (int i=16;i>=0;i--) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i]; for (int i=16;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i]; if (x==y) return x; return fa[x][0]; } int main(){ //freopen("forest.in","r",stdin); //freopen("forest.out","w",stdout); int testcase; scanf("%d",&testcase); scanf("%d%d%d",&n,&m,&t); for (i=1;i<=n;i++){ scanf("%d",&dt[i]); b[i]=dt[i]; } sort(b+1,b+n+1); N=unique(b+1,b+n+1)-b-1; for (i=1;i<=n;i++) dt[i]=lower_bound(b+1,b+N+1,dt[i])-b; for (i=1;i<=m;i++){ scanf("%d%d",&x,&y); add(x,y); } bfs(); char s[3]; int last=0; for (i=1;i<=t;i++){ scanf("%s",s); if (s[0]=='Q'){ scanf("%d%d%d",&x,&y,&k); x^=last; y^=last; k^=last; //printf("Q %d %d %d\n",x,y,k); z=LCA(x,y); zz=fa[z][0]; last=b[ query(T[x],T[y],T[z],T[zz],k) ]; printf("%d\n",last); } else { scanf("%d%d",&x,&y); x^=last; y^=last; //printf("L %d %d\n",x,y); fx=gf(x); fy=gf(y); if (size[fx]>size[fy]) swap(x,y), swap(fx,fy); fa[x][0]=y; for (q[l=r=1]=x, dep[x]=dep[y]+1;l<=r;l++) for (j=a[q[l]];j;j=next[j]) if (fa[q[l]][0]!=node[j]){ fa[ q[++r]=node[j] ][0] = q[l]; dep[q[r]]=dep[q[l]]+1; } size[fy]+=size[fx]; for (j=1;j<17;j++) for (k=1;k<=r;k++) fa[q[k]][j]= fa[ fa[q[k]][j-1] ][j-1]; for (j=1;j<=r;j++) insert(T[ fa[q[j]][0] ],T[q[j]],1,N,dt[q[j]]); add(x,y); } } return 0; }
--------------------------------------------------华丽的分割线--------------------------------------------------
本博客写于2014年12月31日夜。至此2014年已在岁月的长河中轰轰驶过。
作为一位进入OIer,这一年是我茁壮成长的一年,也使我难以忘怀的一年。我经历过不少失败,但是我从未放弃前进,我爱OI,更爱在OI道路上陪伴我的同学们。
让我们挥挥手向2014说声谢谢!
2015年应是笔者最后一年OI生涯了。多少有些不舍。我坚信默默的耕耘总会有收获,我会继续前进,迎来崭新的明天!
让我们挥挥手向2015说声你好!
--------------------------------------------------华丽的分割线--------------------------------------------------