主席树初探

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

看着讲义和网上的博客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说声你好!

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


你可能感兴趣的:(数据结构,主席树)