主席树初探

来源:互联网 发布:php二维数组 编辑:程序博客网 时间:2024/04/30 09:36

笔者近几天研究了一下早已久仰的主席树!(据说可是主席发明的树哦惊讶

看着讲义和网上的博客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;}

2、bzoj3123

注意一下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说声你好!

--------------------------------------------------华丽的分割线--------------------------------------------------


0 0
原创粉丝点击