ACM常用数据结构小结与实现

应当说这段时间学习了很多的数据结构,也到了一个总结的时候。fotile96的这篇Blog非常值得推荐,我达不到这个高度,只能给自己和队友做些简单的归纳。

  树状数组

  非常简单的数据结构,只需要一个数组,一切操作基于如下的函数。需要注意的是,树状数组的下标必须从1开始(从0开始会死循环)。它可以做到在O(logn)时间内完成下述的操作。

  1. int bit[MAXN],n;  
  2. inline int lowbit(int x) {  
  3.     return x&-x;  
  4. }  

  最常见的是增减单点的值,询问前缀和。

  1. void add(int x,int val) {  
  2.     for(int i=x; i<=n; i+=lowbit(i))  
  3.         bit[i]+=val;  
  4. }  
  5. int sum(int x) {  
  6.     int ret=0;  
  7.     for(int i=x; i>0; i-=lowbit(i))  
  8.         ret+=bit[i];  
  9.     return ret;  
  10. }  

  我们注意到,只要维护原序列的差分序列,就可以用上述方式实现对前缀的每一位增减同一个值,询问单点值。不过,其实我们不需要手动将原数组转化为差分序列,只需对函数做一个简单的转化。

  1. void add(int x,int val) {  
  2.     for(int i=x; i>0; i-=lowbit(i))  
  3.         bit[i]+=val;  
  4. }  
  5. int get(int x) {  
  6.     int ret=0;  
  7.     for(int i=x; i<=n; i+=lowbit(i))  
  8.         ret+=bit[i];  
  9.     return ret;  
  10. }  

  树状数组其实是可以实现区间增减,区间求和的。但这个一般还是用线段树来实现。

  1. //修改区间:add(r,val);  
  2. //          if(l>1) add(l-1,-val);  
  3. //查询区间:sum(r)-sum(l-1);  
  4. int bit1[MAXN],bit2[MAXN],n;  
  5. void add(int x,int val) {  
  6.     for(int i=x; i>0; i-=lowbit(i))  
  7.         bit1[i]+=val;  
  8.     for(int i=x; i<=n; i+=lowbit(i))  
  9.         bit2[i]+=x*val;  
  10. }  
  11. int sum(int x) {  
  12.     if(!x)  
  13.         return 0;  
  14.     int ret1=0,ret2=0;  
  15.     for(int i=x; i<=n; i+=lowbit(i))  
  16.         ret1+=bit1[i];  
  17.     for(int i=x-1; i>0; i-=lowbit(i))  
  18.         ret2+=bit2[i];  
  19.     return ret1*x+ret2;  
  20. }  

  树状数组也可以维护最值,但复杂度上升一个log,所以不如用线段树来维护。

  1. void modify(int x,int val) {  
  2.     num[x]=val;  
  3.     for(int i=x; i<=n; i+=lowbit(i)) {  
  4.         bit[i]=max(bit[i],val);  
  5.         for(int j=1; j<lowbit(i); j<<=1)  
  6.             bit[i]=max(bit[i],bit[i-j]);  
  7.     }  
  8. }  
  9. int query(int L,int R) {  
  10.     int ret=num[R];  
  11.     while(true) {  
  12.         ret=max(ret,num[R]);  
  13.         if(L==R)  
  14.             break;  
  15.         for(R-=1; R-L>=lowbit(R); R-=lowbit(R))  
  16.             ret=max(ret,bit[R]);  
  17.     }  
  18.     return ret;  
  19. }  

  线段树

  现在最常见的线段树实现大概有两个版本,一个是NotOnlySuccess的风格,也是我最常使用的风格;它的特点是利用二叉树的数学关系来维护根节点信息。这种写法有一点不足是必须开4倍内存。支持在O(1)时间内将子树信息合并的序列信息理论上都可以用线段树来维护(如最大子段和等),区间维护依靠lazy标记完成以维持O(logn)的单次操作复杂度。

  1. #define lson l,m,rt<<1  
  2. #define rson m+1,r,rt<<1|1  
  3. int sum[MAXN<<2],add[MAXN<<2];  
  4. void push_up(int rt) {  
  5.     sum[rt]=sum[rt<<1]+sum[rt<<1|1];  
  6. }  
  7. void push_down(int rt,int len) {  
  8.     if(add[rt]) {  
  9.         add[rt<<1]+=add[rt];  
  10.         add[rt<<1|1]+=add[rt];  
  11.         sum[rt<<1]+=add[rt]*(len-(len>>1));  
  12.         sum[rt<<1|1]+=add[rt]*(len>>1);  
  13.         add[rt]=0;  
  14.     }  
  15. }  
  16. void build(int l,int r,int rt) {  
  17.     add[rt]=0;  
  18.     if(l==r) {  
  19.         scanf("%d",&sum[rt]);  
  20.         return;  
  21.     }  
  22.     int m=l+r>>1;  
  23.     build(lson);  
  24.     build(rson);  
  25.     push_up(rt);  
  26. }  
  27. void update(int p,int val,int l,int r,int rt) {  
  28.     if(l==r) {  
  29.         sum[rt]+=val;  
  30.         return;  
  31.     }  
  32.     push_down(rt,r-l+1);  
  33.     int m=l+r>>1;  
  34.     if(p<=m)  
  35.         update(p,val,lson);  
  36.     else  
  37.         update(p,val,rson);  
  38.     push_up(rt);  
  39. }  
  40. void update(int L,int R,int val,int l,int r,int rt) {  
  41.     if(L<=l&&r<=R) {  
  42.         add[rt]+=val;  
  43.         sum[rt]+=val*(r-l+1);  
  44.         return;  
  45.     }  
  46.     push_down(rt,r-l+1);  
  47.     int m=l+r>>1;  
  48.     if(L<=m)  
  49.         update(L,R,val,lson);  
  50.     if(m<R)  
  51.         update(L,R,val,rson);  
  52.     push_up(rt);  
  53. }  
  54. int query(int L,int R,int l,int r,int rt) {  
  55.     if(L<=l&&r<=R)  
  56.         return sum[rt];  
  57.     push_down(rt,r-l+1);  
  58.     int m=l+r>>1,ret=0;  
  59.     if(L<=m)  
  60.         ret+=query(L,R,lson);  
  61.     if(m<R)  
  62.         ret+=query(L,R,rson);  
  63.     return ret;  
  64. }  

  另外一种有些类似,与上面不同的是,它使用下面这个神奇的ID函数组织每个节点在数组中的位置。具体实现不再赘述。

  1. inline int ID(int l,int r) {  
  2.     return l+r|l!=r;  
  3. }  

  可持久化线段树

  如果我们想在用数据结构维护信息时,留下所有操作的历史记录,以便回退和查询,我们就需要对数据结构进行可持久化。可持久化的理念非常简单,就是把每次修改节点的操作变成新建新节点的操作,留下了原节点就留下了历史记录。

  线段树是天生利于可持久化的数据结构,原因在于形态的一致性。具体地说,对于以固定方式建立的固定大小的线段树,其形态是完全一致的,且不会因修改而产生变动。为了节约空间,采用了自顶向下的函数式风格,保证了每次更新节点个数在O(logn)的级别,这个做法由黄嘉泰(fotile96)首创,故又称主席树。更具体的介绍可以看我以前的Blog,里面有很多例题,这里不再赘述。

  实时开节点的线段树

  我们注意到可持久化线段树常常被用来维护集合信息而非序列信息,换言之,经常以权值线段树的形式存在,其大小不由数据量决定,而是由数据的范围决定。这样我们便遇到一个困境,对于可持久化线段树这一在线维护信息的利器,却常常需要先离散化才可以使用,从实际上变成了离线做法,面对强制在线的题目非常尴尬。对此,陈立杰(WJMZBMR)在13年国家集训队论文中提到实时开节点的权值线段树;陈立杰的解释有一点抽象,我们可以这样理解,可持久化线段树是先建立初始版本的完整的树,在创建新版本时对修改的节点实时开新节点;而我们现在变成一棵初始状态为一棵空树,从一开始就实时开新节点。

  即使这样说,还是显得不够具体,难以据此实现出实时开节点的线段树,所以这里以BZOJ1901为例,给出一个没有离散化,而是采用实时开节点的可持久化线段树的实现,与这里的代码做一个对比;可以发现实现起来并不麻烦。唯一美中不足是线段树部分单次操作复杂度由O(logn)升为O(logV),其中n是数据量,V是数据范围上界。

  1. #include<cstdio>  
  2. #include<cstring>  
  3. using namespace std;  
  4. #define lson l,m,ls[rt]  
  5. #define rson m+1,r,rs[rt]  
  6. const int MAXN=60005;  
  7. const int MAXM=2500005;  
  8. const int INF=0x3f3f3f3f;  
  9. int ls[MAXM],rs[MAXM],cnt[MAXM],root[MAXN],tot;  
  10. int new_node() {  
  11.     ++tot;  
  12.     ls[tot]=rs[tot]=cnt[tot]=0;  
  13.     return tot;  
  14. }  
  15. void update(int p,int val,int l,int r,int &rt) {  
  16.     if(!rt)  
  17.         rt=new_node();  
  18.     if(l==r) {  
  19.         cnt[rt]+=val;  
  20.         return;  
  21.     }  
  22.     int m=l+r>>1;  
  23.     if(p<=m)  
  24.         update(p,val,lson);  
  25.     else  
  26.         update(p,val,rson);  
  27.     cnt[rt]=cnt[ls[rt]]+cnt[rs[rt]];  
  28. }  
  29. int use[MAXN],n;  
  30. inline int lowbit(int x) {  
  31.     return x&-x;  
  32. }  
  33. void modify(int x,int p,int val) {  
  34.     for(int i=x; i<=n; i+=lowbit(i))  
  35.         update(p,val,0,INF,root[i]);  
  36. }  
  37. int sum(int x) {  
  38.     int ret=0;  
  39.     for(int i=x; i>0; i-=lowbit(i))  
  40.         ret+=cnt[ls[use[i]]];  
  41.     return ret;  
  42. }  
  43. int query(int ss,int tt,int l,int r,int k) {  
  44.     for(int i=ss; i>0; i-=lowbit(i))  
  45.         use[i]=root[i];  
  46.     for(int i=tt; i>0; i-=lowbit(i))  
  47.         use[i]=root[i];  
  48.     while(l<r) {  
  49.         int m=l+r>>1,tmp=sum(tt)-sum(ss);  
  50.         if(k<=tmp) {  
  51.             r=m;  
  52.             for(int i=ss; i>0; i-=lowbit(i))  
  53.                 use[i]=ls[use[i]];  
  54.             for(int i=tt; i>0; i-=lowbit(i))  
  55.                 use[i]=ls[use[i]];  
  56.         } else {  
  57.             l=m+1;  
  58.             k-=tmp;  
  59.             for(int i=ss; i>0; i-=lowbit(i))  
  60.                 use[i]=rs[use[i]];  
  61.             for(int i=tt; i>0; i-=lowbit(i))  
  62.                 use[i]=rs[use[i]];  
  63.         }  
  64.     }  
  65.     return l;  
  66. }  
  67. int a[MAXN];  
  68. int main() {  
  69.     int m,l,r,k;  
  70.     char op;  
  71.     while(~scanf("%d%d",&n,&m)) {  
  72.         tot=0;  
  73.         memset(root,0,sizeof(root));  
  74.         for(int i=1; i<=n; ++i) {  
  75.             scanf("%d",&a[i]);  
  76.             modify(i,a[i],1);  
  77.         }  
  78.         while(m--) {  
  79.             scanf(" %c%d%d",&op,&l,&r);  
  80.             switch(op) {  
  81.             case 'Q':  
  82.                 scanf("%d",&k);  
  83.                 printf("%d\n",query(l-1,r,0,INF,k));  
  84.                 break;  
  85.             case 'C':  
  86.                 modify(l,a[l],-1);  
  87.                 a[l]=r;  
  88.                 modify(l,a[l],1);  
  89.                 break;  
  90.             }  
  91.         }  
  92.     }  
  93. }  

  SkipList(跳表)

  没什么用,这是我对它的评价;它能实现的一切操作用平衡二叉树都可以实现。这里放一个我测过可用,但没花心思修改的版本。

  1. const int MAX_LEVEL=18;  
  2. struct SkipList {  
  3.     struct node {  
  4.         int key,val;  
  5.         node *next[1];  
  6.     };  
  7.     int level;  
  8.     node *head;  
  9.     SkipList() {  
  10.         level=0;  
  11.         head=NewNode(MAX_LEVEL-1,0,0);  
  12.         for(int i=0; i<MAX_LEVEL; ++i)  
  13.             head->next[i]=NULL;  
  14.     }  
  15.     node* NewNode(int level,int key,int val) {  
  16.   
  17.         node *ns=(node *)malloc(sizeof(node)+level*sizeof(node*));  
  18.         ns->key=key;  
  19.         ns->val=val;  
  20.         return ns;  
  21.     }  
  22.     int randomLevel() {  
  23.         int k=1;  
  24.         while(rand()&1)  
  25.             ++k;  
  26.         return k<MAX_LEVEL?k:MAX_LEVEL;  
  27.     }  
  28.     int find(int key) {  
  29.         node *p=head,*q=NULL;  
  30.         for(int i=level-1; i>=0; --i)  
  31.             while((q=p->next[i])&&q->key<=key) {  
  32.                 if(q->key==key)  
  33.                     return q->val;  
  34.                 p=q;  
  35.             }  
  36.         return -INF;  
  37.     }  
  38.     bool insert(int key,int val) {  
  39.         node *update[MAX_LEVEL],*p=head,*q=NULL;  
  40.         for(int i=level-1; i>=0; --i) {  
  41.             while((q=p->next[i])&&q->key<key)  
  42.                 p=q;  
  43.             update[i]=p;  
  44.         }  
  45.         if(q&&q->key==key)  
  46.             return false;  
  47.         int k=randomLevel();  
  48.         if(k>level) {  
  49.             for(int i=level; i<k; ++i)  
  50.                 update[i]=head;  
  51.             level=k;  
  52.         }  
  53.         q=NewNode(k,key,val);  
  54.         for(int i=0; i<k; ++i) {  
  55.             q->next[i]=update[i]->next[i];  
  56.             update[i]->next[i]=q;  
  57.         }  
  58.         return true;  
  59.     }  
  60.     bool erase(int key) {  
  61.         node *update[MAX_LEVEL],*p=head,*q=NULL;  
  62.         for(int i=level-1; i>=0; --i) {  
  63.             while((q=p->next[i])&&q->key<key)  
  64.                 p=q;  
  65.             update[i]=p;  
  66.         }  
  67.         if(q&&q->key==key) {  
  68.             for(int i=0; i<level; ++i)  
  69.                 if(update[i]->next[i]==q)  
  70.                     update[i]->next[i]=q->next[i];  
  71.             free(q);  
  72.             for(int i=level-1; i>=0; --i)  
  73.                 if(head->next[i]==NULL)  
  74.                     --level;  
  75.             return true;  
  76.         }  
  77.         return false;  
  78.     }  
  79.     node* getMin() {  
  80.         return head->next[0];  
  81.     }  
  82.     node* getMax() {  
  83.         node *p=head,*q=NULL;  
  84.         for(int i=level-1; i>=0; --i)  
  85.             while((q=p->next[i])&&q)  
  86.                 p=q;  
  87.         return p==head?NULL:p;  
  88.     }  
  89. };  

  平衡二叉树

  平衡二叉树是维护集合有序性的常用数据结构。我会的平衡二叉树并不多,基本以实用为原则,有Treap、Size Balanced Tree(以下简称SBT)和Splay。它们的很多操作的实现方式完全一致或大同小异,这里为节省篇幅一并放出;注意这些操作适用于不允许重复值的平衡二叉树(set而非multiset),对于允许重复值(拥有cnt域)的实现,只要在一些+1的地方稍作修改(改成cnt[x])即可。一些使用的例题可以在这篇Blog里找到,不再赘述。

  1. bool find(int v) {  
  2.     for(int x=root; x; x=ch[x][key[x]<v])  
  3.         if(key[x]==v)  
  4.             return true;  
  5.     return false;  
  6. }  
  7. int get_kth(int k) {  
  8.     int x=root;  
  9.     while(size[ch[x][0]]+1!=k)  
  10.         if(k<size[ch[x][0]]+1)  
  11.             x=ch[x][0];  
  12.         else {  
  13.             k-=size[ch[x][0]]+1;  
  14.             x=ch[x][1];  
  15.         }  
  16.     return key[x];  
  17. }  
  18. int get_rank(int v) {  
  19.     int ret=0,x=root;  
  20.     while(x)  
  21.         if(v<key[x])  
  22.             x=ch[x][0];  
  23.         else {  
  24.             ret+=size[ch[x][0]]+1;  
  25.             x=ch[x][1];  
  26.         }  
  27.     return ret;  
  28. }  
  29. int get_pre(int v) {  
  30.     int x=root,y=0;  
  31.     while(x)  
  32.         if(v<key[x])  
  33.             x=ch[x][0];  
  34.         else {  
  35.             y=x;  
  36.             x=ch[x][1];  
  37.         }  
  38.     return y;  
  39. }  
  40. int get_next(int v) {  
  41.     int x=root,y=0;  
  42.     while(x)  
  43.         if(v>key[x])  
  44.             x=ch[x][1];  
  45.         else {  
  46.             y=x;  
  47.             x=ch[x][0];  
  48.         }  
  49.     return y;  
  50. }  
  51. int get_min() {  
  52.     if(size[root]==0)  
  53.         return -1;  
  54.     int x=root;  
  55.     while(ch[x][0])  
  56.         x=ch[x][0];  
  57.     return x;  
  58. }  
  59. int get_max() {  
  60.     if(size[root]==0)  
  61.         return -1;  
  62.     int x=root;  
  63.     while(ch[x][1])  
  64.         x=ch[x][1];  
  65.     return x;  
  66. }  
  67. void Treaval(int x) {  
  68.     if(x) {  
  69.         Treaval(ch[x][0]);  
  70.         printf("结点%2d:左儿子 %2d 右儿子 %2d size = %2d ,val = %2d\n",x,ch[x][0],ch[x][1],size[x],key[x]);  
  71.         Treaval(ch[x][1]);  
  72.     }  
  73. }  
  74. void debug() {  
  75.     printf("root:%d\n",root);  
  76.     Treaval(root);  
  77.     putchar('\n');  
  78. }  

  基于旋转的Treap

  Treap大概是赛场上最常见的平衡二叉树,它同时维护序列的有序性和堆的性质,依靠堆值的随机化,将树的高度维护在期望下平衡的程度,从而实现了各种操作期望O(logn)的复杂度。它的性价比高在只有两种旋转(而且可以合并地写),比红黑树和AVL短小;又因为复杂度基于期望而非均摊,在各种数据下都有良好的表现。这里只给出允许重复值的实现。

  1. struct Treap {  
  2.     int tot,root;  
  3.     int ch[MAXN][2],key[MAXN],pt[MAXN],cnt[MAXN],size[MAXN];  
  4.     void init() {  
  5.         tot=root=0;  
  6.         pt[0]=INF;  
  7.     }  
  8.     void push_up(int x) {  
  9.         size[x]=size[ch[x][0]]+size[ch[x][1]]+cnt[x];  
  10.     }  
  11.     void new_node(int &x,int v) {  
  12.         x=++tot;  
  13.         ch[x][0]=ch[x][1]=0;  
  14.         size[x]=cnt[x]=1;  
  15.         pt[x]=rand();  
  16.         key[x]=v;  
  17.     }  
  18.     void rotate(int &x,int f) {  
  19.         int y=ch[x][f];  
  20.         ch[x][f]=ch[y][f^1];  
  21.         ch[y][f^1]=x;  
  22.         push_up(x);  
  23.         push_up(y);  
  24.         x=y;  
  25.     }  
  26.     void insert(int &x,int v) {  
  27.         if(!x) {  
  28.             new_node(x,v);  
  29.             return;  
  30.         }  
  31.         if(key[x]==v)  
  32.             ++cnt[x];  
  33.         else {  
  34.             int f=key[x]<v;  
  35.             insert(ch[x][f],v);  
  36.             if(pt[ch[x][f]]<pt[x])  
  37.                 rotate(x,f);  
  38.         }  
  39.         push_up(x);  
  40.     }  
  41.     void erase(int &x,int v) {  
  42.         if(!x)  
  43.             return;  
  44.         if(key[x]==v) {  
  45.             if(cnt[x]>1)  
  46.                 --cnt[x];  
  47.             else {  
  48.                 if(!ch[x][0]&&!ch[x][1])  
  49.                     x=0;  
  50.                 else {  
  51.                     rotate(x,pt[ch[x][0]]>pt[ch[x][1]]);  
  52.                     erase(x,v);  
  53.                 }  
  54.             }  
  55.         } else  
  56.             erase(ch[x][key[x]<v],v);  
  57.         push_up(x);  
  58.     }  
  59.     void insert(int v) {  
  60.         insert(root,v);  
  61.     }  
  62.     void erase(int v) {  
  63.         erase(root,v);  
  64.     }  
  65. };  

  Size Balanced Tree

  这个由陈启峰发明的数据结构依靠其独特的平摊时间O(1)的Maintain操作,具有仅次于红黑树的优秀的时间效率,但由于赛场上Treap已经够用,也因为有人指出陈启峰在复杂度的证明上有漏洞(有人声称某些数据可以使SBT退化成人字形,可以想象一下成串的鞭炮的样子),使用的人并不多。我也只是大致学习了一下,用的次数并不多。这里给出不允许重复值的实现。

  1. struct SBT {  
  2.     int root,tot;  
  3.     int ch[MAXN][2],key[MAXN],size[MAXN];  
  4.     void init() {  
  5.         tot=root=0;  
  6.         size[0]=0;  
  7.     }  
  8.     void rotate(int &x,int f) {  
  9.         int y=ch[x][f];  
  10.         ch[x][f]=ch[y][f^1];  
  11.         ch[y][f^1]=x;  
  12.         size[y]=size[x];  
  13.         size[x]=size[ch[x][0]]+size[ch[x][1]]+1;  
  14.         x=y;  
  15.     }  
  16.     void maintain(int &x,int f) {  
  17.         if(size[ch[ch[x][f]][f]]>size[ch[x][f^1]])  
  18.             rotate(x,f);  
  19.         else if(size[ch[ch[x][f]][f^1]]>size[ch[x][f^1]]) {  
  20.             rotate(ch[x][f],f^1);  
  21.             rotate(x,f);  
  22.         } else  
  23.             return;  
  24.         maintain(ch[x][0],0);  
  25.         maintain(ch[x][1],1);  
  26.         maintain(x,0);  
  27.         maintain(x,1);  
  28.     }  
  29.     void insert(int &x,int v) {  
  30.         if(!x) {  
  31.             x=++tot;  
  32.             ch[x][0]=ch[x][1]=0;  
  33.             size[x]=1;  
  34.             key[x]=v;  
  35.         } else {  
  36.             ++size[x];  
  37.             insert(ch[x][key[x]<v],v);  
  38.             maintain(x,key[x]<v);  
  39.         }  
  40.     }  
  41.     int erase(int &x,int v) {  
  42.         if(!x)  
  43.             return 0;  
  44.         --size[x];  
  45.         if(key[x]==v||(key[x]>v&&!ch[x][0])||(key[x]<v&&!ch[x][1])) {  
  46.             int ret=key[x];  
  47.             if(ch[x][0]&&ch[x][1])  
  48.                 key[x]=erase(ch[x][0],v+1);  
  49.             else  
  50.                 x=ch[x][0]+ch[x][1];  
  51.             return ret;  
  52.         }  
  53.         return erase(ch[x][key[x]<v],v);  
  54.     }  
  55.     void insert(int v) {  
  56.         insert(root,v);  
  57.     }  
  58.     void erase(int v) {  
  59.         erase(root,v);  
  60.     }  
  61. };  

  Splay(伸展树)

  Splay因其独特性的splay操作(将某一节点旋转到另一节点下)而得名,是一种非常灵活的,在现实生活中应用也非常广泛的二叉查找树;称之为平衡二叉树是不太严谨的,因为它从来不保证高度平衡,而是每次将访问过的节点旋转到根。也因这一操作,Splay变得异常强大,可以实现很多其它平衡树无法实现的操作(如区间翻转),它更像平衡树和线段树的杂糅,既可以维护集合信息,也可以维护序列信息,可以用它来做Treap的题,也可以用它来做线段树的题。更重要的是,Splay可以实现split(将某棵子树从原树中分离)和merge操作(将某棵子树插入另一棵树),这也使得区间插入删除成为可能。它的美中不足是常数稍大,约是Treap的1.5~3倍,线段树的2~5倍。

  Splay有单旋和双旋两种实现,其中只有双旋保证了均摊O(logn)的单次操作复杂度,但因为很多人认为zigzag太长不好敲(大多是OI选手有此困扰),选择了单旋。其实完全可以稍微损失一点常数,合并成一个rotate函数来完成双旋。此外一个良好的实现通常要在序列一首一尾增加两个哨兵节点,这样可以减少很多边界特判。

  有必要进行的扩展性说明是,对于一棵树,如果想要维护子树信息,我们可以用Splay维护这棵树的括号序列(dfs序),这样便可以轻易split出任意子树所属的区间;而用Splay维护dfs序的结构,就是Euler-Tour Tree。同样的,如果想要维护链上信息,可以先树链剖分然后用Splay维护每条重链,根据杨哲在07年国家集训队作业的计算,因其势能分析得到的复杂度依然是单次操作均摊O(logn)复杂度;而类似的思想做些转化,就变成了后面会提到的Link-Cut Tree(以下简称LCT)。

  这里给出了POJ3580的Splay实现。注意我这次erase函数写了内存回收,这一做法完全可以照搬到其它平衡树中。

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4. #define keyTree (ch[ch[root][1]][0])  
  5. const int MAXN=200005;  
  6. const int INF=0x3f3f3f3f;  
  7. int num[MAXN];  
  8. struct SplayTree {  
  9.     int root,tot1,tot2;  
  10.     int ch[MAXN][2],pre[MAXN],size[MAXN];  
  11.     int gc[MAXN],que[MAXN];  
  12.     int key[MAXN],vmin[MAXN],add[MAXN],rev[MAXN];  
  13.     void rotate(int x,int f) {  
  14.         int y=pre[x];  
  15.         ch[y][f^1]=ch[x][f];  
  16.         pre[ch[x][f]]=y;  
  17.         pre[x]=pre[y];  
  18.         if(pre[x])  
  19.             ch[pre[y]][ch[pre[y]][1]==y]=x;  
  20.         ch[x][f]=y;  
  21.         pre[y]=x;  
  22.         push_up(y);  
  23.     }  
  24.     void splay(int x,int goal) {  
  25.         push_down(x);  
  26.         while(pre[x]!=goal) {  
  27.             int y=pre[x],z=pre[y];  
  28.             if(z==goal) {  
  29.                 push_down(y);  
  30.                 push_down(x);  
  31.                 rotate(x,ch[y][0]==x);  
  32.             } else {  
  33.                 push_down(z);  
  34.                 push_down(y);  
  35.                 push_down(x);  
  36.                 int f=ch[z][0]==y;  
  37.                 if(ch[y][f]==x)  
  38.                     rotate(x,f^1);  
  39.                 else  
  40.                     rotate(y,f);  
  41.                 rotate(x,f);  
  42.             }  
  43.         }  
  44.         push_up(x);  
  45.         if(goal==0)  
  46.             root=x;  
  47.     }  
  48.     void rotate_to(int k,int goal) {  
  49.         int x=root;  
  50.         push_down(x);  
  51.         while(size[ch[x][0]]!=k) {  
  52.             if(k<size[ch[x][0]])  
  53.                 x=ch[x][0];  
  54.             else {  
  55.                 k-=size[ch[x][0]]+1;  
  56.                 x=ch[x][1];  
  57.             }  
  58.             push_down(x);  
  59.         }  
  60.         splay(x,goal);  
  61.     }  
  62.     void erase(int x) {  
  63.         int fa=pre[x],head=0,tail=0;  
  64.         for(que[tail++]=x; head<tail; ++head) {  
  65.             gc[tot2++]=que[head];  
  66.             if(ch[que[head]][0])  
  67.                 que[tail++]=ch[que[head]][0];  
  68.             if(ch[que[head]][1])  
  69.                 que[tail++]=ch[que[head]][1];  
  70.         }  
  71.         ch[fa][ch[fa][1]==x]=0;  
  72.         push_up(fa);  
  73.     }  
  74.     void new_node(int &x,int v,int fa) {  
  75.         if(tot2)  
  76.             x=gc[--tot2];  
  77.         else  
  78.             x=++tot1;  
  79.         ch[x][0]=ch[x][1]=0;  
  80.         pre[x]=fa;  
  81.         size[x]=1;  
  82.         key[x]=vmin[x]=v;  
  83.         add[x]=rev[x]=0;  
  84.     }  
  85.     void update_add(int x,int d) {  
  86.         if(x) {  
  87.             key[x]+=d;  
  88.             add[x]+=d;  
  89.             vmin[x]+=d;  
  90.         }  
  91.     }  
  92.     void update_rev(int x) {  
  93.         if(x) {  
  94.             swap(ch[x][0],ch[x][1]);  
  95.             rev[x]^=1;  
  96.         }  
  97.     }  
  98.     void push_up(int x) {  
  99.         size[x]=size[ch[x][0]]+size[ch[x][1]]+1;  
  100.         vmin[x]=min(key[x],min(vmin[ch[x][0]],vmin[ch[x][1]]));  
  101.     }  
  102.     void push_down(int x) {  
  103.         if(add[x]) {  
  104.             update_add(ch[x][0],add[x]);  
  105.             update_add(ch[x][1],add[x]);  
  106.             add[x]=0;  
  107.         }  
  108.         if(rev[x]) {  
  109.             update_rev(ch[x][0]);  
  110.             update_rev(ch[x][1]);  
  111.             rev[x]=0;  
  112.         }  
  113.     }  
  114.     void build(int &x,int l,int r,int f) {  
  115.         int m=l+r>>1;  
  116.         new_node(x,num[m],f);  
  117.         if(l<m)  
  118.             build(ch[x][0],l,m-1,x);  
  119.         if(r>m)  
  120.             build(ch[x][1],m+1,r,x);  
  121.         push_up(x);  
  122.     }  
  123.     void init(int n) {  
  124.         root=tot1=tot2=0;  
  125.         ch[0][0]=ch[0][1]=pre[0]=size[0]=0;  
  126.         add[0]=rev[0]=0;  
  127.         key[0]=vmin[0]=INF;  
  128.         new_node(root,-1,0);  
  129.         new_node(ch[root][1],-1,root);  
  130.         size[root]=2;  
  131.         for(int i=1; i<=n; ++i)  
  132.             scanf("%d",&num[i]);  
  133.         build(keyTree,1,n,ch[root][1]);  
  134.         push_up(ch[root][1]);  
  135.         push_up(root);  
  136.     }  
  137.     void plus(int l,int r,int v) {  
  138.         rotate_to(l-1,0);  
  139.         rotate_to(r+1,root);  
  140.         update_add(keyTree,v);  
  141.     }  
  142.     void reverse(int l,int r) {  
  143.         rotate_to(l-1,0);  
  144.         rotate_to(r+1,root);  
  145.         update_rev(keyTree);  
  146.     }  
  147.     void revolve(int l,int r,int k) {  
  148.         k%=r-l+1;  
  149.         if(!k)  
  150.             return;  
  151.         rotate_to(r-k,0);  
  152.         rotate_to(r+1,root);  
  153.         int tmp=keyTree;  
  154.         keyTree=0;  
  155.         push_up(ch[root][1]);  
  156.         push_up(root);  
  157.         rotate_to(l-1,0);  
  158.         rotate_to(l,root);  
  159.         keyTree=tmp;  
  160.         pre[tmp]=ch[root][1];  
  161.         push_up(ch[root][1]);  
  162.         push_up(root);  
  163.     }  
  164.     void insert(int k,int v) {  
  165.         rotate_to(k,0);  
  166.         rotate_to(k+1,root);  
  167.         new_node(keyTree,v,ch[root][1]);  
  168.         push_up(ch[root][1]);  
  169.         push_up(root);  
  170.     }  
  171.     void del(int k) {  
  172.         rotate_to(k-1,0);  
  173.         rotate_to(k+1,root);  
  174.         erase(keyTree);  
  175.         push_up(ch[root][1]);  
  176.         push_up(root);  
  177.     }  
  178.     int query(int l,int r) {  
  179.         rotate_to(l-1,0);  
  180.         rotate_to(r+1,root);  
  181.         return vmin[keyTree];  
  182.     }  
  183. } splay;  
  184. int main() {  
  185.     int n,m,x,y,v;  
  186.     char op[10];  
  187.     while(~scanf("%d",&n)) {  
  188.         splay.init(n);  
  189.         scanf("%d",&m);  
  190.         while(m--) {  
  191.             scanf("%s",op);  
  192.             switch(op[0]) {  
  193.             case 'A':  
  194.                 scanf("%d%d%d",&x,&y,&v);  
  195.                 splay.plus(x,y,v);  
  196.                 break;  
  197.             case 'R':  
  198.                 scanf("%d%d",&x,&y);  
  199.                 if(op[3]=='E')  
  200.                     splay.reverse(x,y);  
  201.                 else {  
  202.                     scanf("%d",&v);  
  203.                     splay.revolve(x,y,v);  
  204.                 }  
  205.                 break;  
  206.             case 'I':  
  207.                 scanf("%d%d",&x,&v);  
  208.                 splay.insert(x,v);  
  209.                 break;  
  210.             case 'D':  
  211.                 scanf("%d",&x);  
  212.                 splay.del(x);  
  213.                 break;  
  214.             case 'M':  
  215.                 scanf("%d%d",&x,&y);  
  216.                 printf("%d\n",splay.query(x,y));  
  217.                 break;  
  218.             }  
  219.         }  
  220.     }  
  221. }  

  额外的更新。哨兵节点的存在有利有弊,在对树进行split和merge的时候有时会出现一些调试上的不便,在某些时候显得代码不够优雅。由此,我参照交大板,结合LCT的一些函数写了新的实现。以下是HDU4453的Splay实现,当然用上面的做法和后面提到的不基于旋转的Treap同样可做。

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4. const int MAXN=200005;  
  5. int k1,k2,num[MAXN];  
  6. struct Splay {  
  7.     int root,tot,point;  
  8.     int ch[MAXN][2],pre[MAXN],size[MAXN];  
  9.     int key[MAXN],add[MAXN],rev[MAXN];  
  10.     bool isroot(int x) {  
  11.         return !pre[x]||ch[pre[x]][0]!=x&&ch[pre[x]][1]!=x;  
  12.     }  
  13.     void rotate(int x) {  
  14.         int y=pre[x],f=ch[y][1]==x;  
  15.         ch[y][f]=ch[x][f^1];  
  16.         pre[ch[y][f]]=y;  
  17.         if(!isroot(y))  
  18.             ch[pre[y]][ch[pre[y]][1]==y]=x;  
  19.         pre[x]=pre[y];  
  20.         ch[x][f^1]=y;  
  21.         pre[y]=x;  
  22.         push_up(y);  
  23.     }  
  24.     void splay(int x) {  
  25.         push_down(x);  
  26.         while(!isroot(x)) {  
  27.             int y=pre[x],z=pre[y];  
  28.             if(isroot(y)) {  
  29.                 push_down(y);  
  30.                 push_down(x);  
  31.                 rotate(x);  
  32.             } else {  
  33.                 push_down(z);  
  34.                 push_down(y);  
  35.                 push_down(x);  
  36.                 rotate((ch[z][1]==y)==(ch[y][1]==x)?y:x);  
  37.                 rotate(x);  
  38.             }  
  39.         }  
  40.         push_up(x);  
  41.     }  
  42.     void new_node(int &x,int v,int fa) {  
  43.         x=++tot;  
  44.         ch[x][0]=ch[x][1]=0;  
  45.         pre[x]=fa;  
  46.         size[x]=1;  
  47.         key[x]=v;  
  48.         add[x]=rev[x]=0;  
  49.     }  
  50.     void update_add(int x,int v) {  
  51.         if(x) {  
  52.             key[x]+=v;  
  53.             add[x]+=v;  
  54.         }  
  55.     }  
  56.     void update_rev(int x) {  
  57.         if(x) {  
  58.             rev[x]^=1;  
  59.             swap(ch[x][0],ch[x][1]);  
  60.         }  
  61.     }  
  62.     void push_down(int x) {  
  63.         if(add[x]) {  
  64.             update_add(ch[x][0],add[x]);  
  65.             update_add(ch[x][1],add[x]);  
  66.             add[x]=0;  
  67.         }  
  68.         if(rev[x]) {  
  69.             update_rev(ch[x][0]);  
  70.             update_rev(ch[x][1]);  
  71.             rev[x]=0;  
  72.         }  
  73.     }  
  74.     void push_up(int x) {  
  75.         size[x]=size[ch[x][0]]+size[ch[x][1]]+1;  
  76.     }  
  77.     void build(int &x,int l,int r,int fa) {  
  78.         int m=l+r>>1;  
  79.         new_node(x,num[m],fa);  
  80.         if(l<m)  
  81.             build(ch[x][0],l,m-1,x);  
  82.         if(r>m)  
  83.             build(ch[x][1],m+1,r,x);  
  84.         push_up(x);  
  85.     }  
  86.     void init(int n) {  
  87.         root=tot=size[0]=0;  
  88.         for(int i=1; i<=n; ++i)  
  89.             scanf("%d",&num[i]);  
  90.         build(root,1,n,0);  
  91.         point=1;  
  92.     }  
  93.     int find(int rt,int k) {  
  94.         int x=rt;  
  95.         while(size[ch[x][0]]+1!=k) {  
  96.             push_down(x);  
  97.             if(k<=size[ch[x][0]])  
  98.                 x=ch[x][0];  
  99.             else {  
  100.                 k-=size[ch[x][0]]+1;  
  101.                 x=ch[x][1];  
  102.             }  
  103.         }  
  104.         return x;  
  105.     }  
  106.     void split(int &x,int &y,int sz) {  
  107.         if(!sz) {  
  108.             y=x;  
  109.             x=0;  
  110.             return;  
  111.         }  
  112.         y=find(x,sz+1);  
  113.         splay(y);  
  114.         x=ch[y][0];  
  115.         ch[y][0]=0;  
  116.         push_up(y);  
  117.     }  
  118.     void split3(int &x,int &y,int &z,int l,int r) {  
  119.         split(x,z,r);  
  120.         split(x,y,l-1);  
  121.     }  
  122.     void join(int &x,int &y) {  
  123.         if(!x||!y) {  
  124.             x|=y;  
  125.             return;  
  126.         }  
  127.         x=find(x,size[x]);  
  128.         splay(x);  
  129.         ch[x][1]=y;  
  130.         pre[y]=x;  
  131.         push_up(x);  
  132.     }  
  133.     void join3(int &x,int y,int z) {  
  134.         join(y,z);  
  135.         join(x,y);  
  136.     }  
  137.     void evert() {  
  138.         if(point>1) {  
  139.             int x;  
  140.             split(root,x,point-1);  
  141.             swap(root,x);  
  142.             join(root,x);  
  143.             point=1;  
  144.         }  
  145.     }  
  146.     void plus(int v) {  
  147.         evert();  
  148.         int x,y;  
  149.         split3(root,x,y,point,point+k2-1);  
  150.         update_add(x,v);  
  151.         join3(root,x,y);  
  152.     }  
  153.     void reverse() {  
  154.         evert();  
  155.         int x,y;  
  156.         split3(root,x,y,point,point+k1-1);  
  157.         update_rev(x);  
  158.         join3(root,x,y);  
  159.     }  
  160.     void insert(int v) {  
  161.         evert();  
  162.         int x,y;  
  163.         split(root,x,point);  
  164.         new_node(y,v,0);  
  165.         join3(root,y,x);  
  166.     }  
  167.     void erase() {  
  168.         evert();  
  169.         int x,y;  
  170.         split3(root,x,y,point,point);  
  171.         join(root,y);  
  172.     }  
  173.     void move(int tag) {  
  174.         switch(tag) {  
  175.         case 1:  
  176.             if(--point==0)  
  177.                 point=size[root];  
  178.             break;  
  179.         case 2:  
  180.             if(++point==size[root]+1)  
  181.                 point=1;  
  182.             break;  
  183.         }  
  184.     }  
  185.     void query() {  
  186.         evert();  
  187.         int x,y;  
  188.         split3(root,x,y,point,point);  
  189.         printf("%d\n",key[x]);  
  190.         join3(root,x,y);  
  191.     }  
  192. } splay;  
  193. int main() {  
  194.     int n,m,v,cas=0;  
  195.     char op[10];  
  196.     while(~scanf("%d%d%d%d",&n,&m,&k1,&k2)&&(n||m||k1||k2)) {  
  197.         splay.init(n);  
  198.         printf("Case #%d:\n",++cas);  
  199.         while(m--) {  
  200.             scanf("%s",op);  
  201.             switch(op[0]) {  
  202.             case 'a':  
  203.                 scanf("%d",&v);  
  204.                 splay.plus(v);  
  205.                 break;  
  206.             case 'r':  
  207.                 splay.reverse();  
  208.                 break;  
  209.             case 'i':  
  210.                 scanf("%d",&v);  
  211.                 splay.insert(v);  
  212.                 break;  
  213.             case 'd':  
  214.                 splay.erase();  
  215.                 break;  
  216.             case 'm':  
  217.                 scanf("%d",&v);  
  218.                 splay.move(v);  
  219.                 break;  
  220.             case 'q':  
  221.                 splay.query();  
  222.                 break;  
  223.             }  
  224.         }  
  225.     }  
  226. }  

  Link-Cut Tree

  动态树是维护森林信息的一类问题的总称。最常见的也是我唯一会的就是LCT;此外还有自适应Top Tree、全局平衡二叉树等。LCT可以维护多棵树(森林)的形态,并在O(logn)的时间复杂度内维护链上信息;但LCT处理子树信息将会非常麻烦。它的核心操作是access函数,可以把某个节点到根的路径上所有点按照深度用Splay维护起来,从而结合evert函数(换跟操作)和splay操作可以实现对链的信息维护。LCT几乎可以实现除维护子树信息外以上的所有操作,同时有着优越的理论复杂度,但实际常数较大,很多不改变树形态的题用O(logn)的LCT并不比O(log^2n)的树链剖分套线段树更优越。以下是HDU4010的LCT实现。

  1. #include<cstdio>  
  2. #include<cstring>  
  3. #include<algorithm>  
  4. using namespace std;  
  5. const int MAXN=300005;  
  6. struct LCT {  
  7.     int ch[MAXN][2],pre[MAXN],key[MAXN],rev[MAXN];  
  8.     int add[MAXN],vmax[MAXN];  
  9.     bool isroot(int x) {  
  10.         return !pre[x]||ch[pre[x]][0]!=x&&ch[pre[x]][1]!=x;  
  11.     }  
  12.     void rotate(int x) {  
  13.         int y=pre[x],f=ch[y][1]==x;  
  14.         ch[y][f]=ch[x][f^1];  
  15.         pre[ch[y][f]]=y;  
  16.         if(!isroot(y))  
  17.             ch[pre[y]][ch[pre[y]][1]==y]=x;  
  18.         pre[x]=pre[y];  
  19.         ch[x][f^1]=y;  
  20.         pre[y]=x;  
  21.         push_up(y);  
  22.     }  
  23.     void splay(int x) {  
  24.         push_down(x);  
  25.         while(!isroot(x)) {  
  26.             int y=pre[x],z=pre[y];  
  27.             if(isroot(y)) {  
  28.                 push_down(y);  
  29.                 push_down(x);  
  30.                 rotate(x);  
  31.             } else {  
  32.                 push_down(z);  
  33.                 push_down(y);  
  34.                 push_down(x);  
  35.                 rotate((ch[z][1]==y)==(ch[y][1]==x)?y:x);  
  36.                 rotate(x);  
  37.             }  
  38.         }  
  39.         push_up(x);  
  40.     }  
  41.     int access(int x) {  
  42.         int y=0;  
  43.         for(; x; x=pre[x]) {  
  44.             splay(x);  
  45.             ch[x][1]=y;  
  46.             push_up(x);  
  47.             y=x;  
  48.         }  
  49.         return y;  
  50.     }  
  51.     void evert(int x) {  
  52.         rev[access(x)]^=1;  
  53.         splay(x);  
  54.     }  
  55.     void push_up(int x) {  
  56.         vmax[x]=max(max(vmax[ch[x][0]],vmax[ch[x][1]]),key[x]);  
  57.     }  
  58.     void push_down(int x) {  
  59.         if(add[x]) {  
  60.             key[x]+=add[x];  
  61.             if(ch[x][0]) {  
  62.                 add[ch[x][0]]+=add[x];  
  63.                 vmax[ch[x][0]]+=add[x];  
  64.             }  
  65.             if(ch[x][1]) {  
  66.                 add[ch[x][1]]+=add[x];  
  67.                 vmax[ch[x][1]]+=add[x];  
  68.             }  
  69.             add[x]=0;  
  70.         }  
  71.         if(rev[x]) {  
  72.             if(ch[x][0])  
  73.                 rev[ch[x][0]]^=1;  
  74.             if(ch[x][1])  
  75.                 rev[ch[x][1]]^=1;  
  76.             swap(ch[x][0],ch[x][1]);  
  77.             rev[x]=0;  
  78.         }  
  79.     }  
  80.     int find_root(int x) {  
  81.         while(pre[x])  
  82.             x=pre[x];  
  83.         return x;  
  84.     }  
  85.     void link(int u,int v) {  
  86.         if(find_root(u)==find_root(v)) {  
  87.             puts("-1");  
  88.             return;  
  89.         }  
  90.         evert(u);  
  91.         pre[u]=v;  
  92.     }  
  93.     void cut(int u,int v) {  
  94.         if(u==v||find_root(u)!=find_root(v)) {  
  95.             puts("-1");  
  96.             return;  
  97.         }  
  98.         evert(u);  
  99.         access(v);  
  100.         splay(v);  
  101.         pre[ch[v][0]]=0;  
  102.         ch[v][0]=0;  
  103.         push_up(v);  
  104.     }  
  105.     void update(int u,int v,int w) {  
  106.         if(find_root(u)!=find_root(v)) {  
  107.             puts("-1");  
  108.             return;  
  109.         }  
  110.         evert(u);  
  111.         access(v);  
  112.         splay(v);  
  113.         add[v]+=w;  
  114.         vmax[v]+=w;  
  115.         push_down(v);  
  116.     }  
  117.     void query(int u,int v) {  
  118.         if(find_root(u)!=find_root(v)) {  
  119.             puts("-1");  
  120.             return;  
  121.         }  
  122.         evert(u);  
  123.         access(v);  
  124.         splay(v);  
  125.         printf("%d\n",vmax[v]);  
  126.     }  
  127.     struct graph {  
  128.         int head[MAXN],to[MAXN<<1],next[MAXN<<1];  
  129.         int tot;  
  130.         void init() {  
  131.             tot=0;  
  132.             memset(head,0xff,sizeof(head));  
  133.         }  
  134.         void add(int u,int v) {  
  135.             to[tot]=v;  
  136.             next[tot]=head[u];  
  137.             head[u]=tot++;  
  138.         }  
  139.     } g;  
  140.     void dfs(int u,int fa) {  
  141.         for(int i=g.head[u]; ~i; i=g.next[i]) {  
  142.             int v=g.to[i];  
  143.             if(v!=fa) {  
  144.                 dfs(v,u);  
  145.                 pre[v]=u;  
  146.             }  
  147.         }  
  148.     }  
  149.     void init(int n) {  
  150.         int m,x,y;  
  151.         g.init();  
  152.         for(int i=1; i<n; ++i) {  
  153.             scanf("%d%d",&x,&y);  
  154.             g.add(x,y);  
  155.             g.add(y,x);  
  156.         }  
  157.         memset(ch,0,sizeof(ch));  
  158.         memset(pre,0,sizeof(pre));  
  159.         memset(rev,0,sizeof(rev));  
  160.         memset(add,0,sizeof(add));  
  161.         vmax[0]=0;  
  162.         for(int i=1; i<=n; ++i) {  
  163.             scanf("%d",&key[i]);  
  164.             vmax[i]=key[i];  
  165.         }  
  166.         dfs(1,0);  
  167.     }  
  168. } lct;  
  169. int main() {  
  170.     int n,q,op,x,y,w;  
  171.     while(~scanf("%d",&n)) {  
  172.         lct.init(n);  
  173.         scanf("%d",&q);  
  174.         while(q--) {  
  175.             scanf("%d",&op);  
  176.             switch(op) {  
  177.             case 1:  
  178.                 scanf("%d%d",&x,&y);  
  179.                 lct.link(x,y);  
  180.                 break;  
  181.             case 2:  
  182.                 scanf("%d%d",&x,&y);  
  183.                 lct.cut(x,y);  
  184.                 break;  
  185.             case 3:  
  186.                 scanf("%d%d%d",&w,&x,&y);  
  187.                 lct.update(x,y,w);  
  188.                 break;  
  189.             case 4:  
  190.                 scanf("%d%d",&x,&y);  
  191.                 lct.query(x,y);  
  192.                 break;  
  193.             }  
  194.         }  
  195.         putchar('\n');  
  196.     }  
  197. }  

  不基于旋转的Treap

  以上所有平衡树都利用旋转操作来调整树的高度,以保持严格或期望或均摊O(logn)的单次操作复杂度,但基于旋转的结构有个很大的弊端是不支持可持久化。由此,范浩强(fanhq666)首先在这篇Blog里提出了不基于旋转的Treap。从Splay中我们已经可以意识到,只要设计出O(logn)的split和merge操作,便可以在相同时间复杂度内完成平衡树的一切基本操作。算法和理论证明可以看范浩强的Blog和上面提到的陈立杰的论文。这样这种Treap已经可以完成Splay的一切操作,只是据不多的测试,并不比Splay快。以下是POJ3580的Treap实现,可与上述Splay实现做对比。

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4. const int MAXN=200005;  
  5. int num[MAXN];  
  6. struct Treap {  
  7.     int tot,root;  
  8.     int ch[MAXN][2],pt[MAXN],size[MAXN];  
  9.     int key[MAXN],vmin[MAXN],add[MAXN],rev[MAXN];  
  10.     void init() {  
  11.         tot=0;  
  12.     }  
  13.     void new_node(int &x,int v) {  
  14.         x=++tot;  
  15.         ch[x][0]=ch[x][1]=0;  
  16.         size[x]=1;  
  17.         pt[x]=rand();  
  18.         key[x]=vmin[x]=v;  
  19.         add[x]=rev[x]=0;  
  20.     }  
  21.     void merge(int &p,int x,int y) {  
  22.         if(!x||!y) {  
  23.             p=x|y;  
  24.             return;  
  25.         }  
  26.         if(pt[x]<pt[y]) {  
  27.             push_down(x);  
  28.             merge(ch[x][1],ch[x][1],y);  
  29.             p=x;  
  30.         } else {  
  31.             push_down(y);  
  32.             merge(ch[y][0],x,ch[y][0]);  
  33.             p=y;  
  34.         }  
  35.         push_up(p);  
  36.     }  
  37.     void split(int p,int sz,int &x,int &y) {  
  38.         if(!sz) {  
  39.             x=0;  
  40.             y=p;  
  41.             return;  
  42.         }  
  43.         push_down(p);  
  44.         if(size[ch[p][0]]>=sz) {  
  45.             y=p;  
  46.             split(ch[p][0],sz,x,ch[y][0]);  
  47.         } else {  
  48.             x=p;  
  49.             split(ch[p][1],sz-size[ch[p][0]]-1,ch[x][1],y);  
  50.         }  
  51.         push_up(p);  
  52.     }  
  53.     void update_add(int x,int v) {  
  54.         if(x) {  
  55.             key[x]+=v;  
  56.             add[x]+=v;  
  57.             vmin[x]+=v;  
  58.         }  
  59.     }  
  60.     void update_rev(int x) {  
  61.         if(x) {  
  62.             swap(ch[x][0],ch[x][1]);  
  63.             rev[x]^=1;  
  64.         }  
  65.     }  
  66.     void push_down(int x) {  
  67.         if(add[x]) {  
  68.             update_add(ch[x][0],add[x]);  
  69.             update_add(ch[x][1],add[x]);  
  70.             add[x]=0;  
  71.         }  
  72.         if(rev[x]) {  
  73.             update_rev(ch[x][0]);  
  74.             update_rev(ch[x][1]);  
  75.             rev[x]=0;  
  76.         }  
  77.     }  
  78.     void push_up(int x) {  
  79.         size[x]=1;  
  80.         vmin[x]=key[x];  
  81.         if(ch[x][0]) {  
  82.             size[x]+=size[ch[x][0]];  
  83.             vmin[x]=min(vmin[x],vmin[ch[x][0]]);  
  84.         }  
  85.         if(ch[x][1]) {  
  86.             size[x]+=size[ch[x][1]];  
  87.             vmin[x]=min(vmin[x],vmin[ch[x][1]]);  
  88.         }  
  89.     }  
  90.     int build(int &x,int l,int r) {  
  91.         int m=l+r>>1;  
  92.         new_node(x,num[m]);  
  93.         if(l<m)  
  94.             build(ch[x][0],l,m-1);  
  95.         if(r>m)  
  96.             build(ch[x][1],m+1,r);  
  97.         push_up(x);  
  98.     }  
  99.     void plus(int l,int r,int v) {  
  100.         int x,y;  
  101.         split(root,l-1,root,x);  
  102.         split(x,r-l+1,x,y);  
  103.         update_add(x,v);  
  104.         merge(x,x,y);  
  105.         merge(root,root,x);  
  106.     }  
  107.     void reverse(int l,int r) {  
  108.         int x,y;  
  109.         split(root,l-1,root,x);  
  110.         split(x,r-l+1,x,y);  
  111.         update_rev(x);  
  112.         merge(x,x,y);  
  113.         merge(root,root,x);  
  114.     }  
  115.     void revolve(int l,int r,int k) {  
  116.         int x,y,p,q;  
  117.         k%=r-l+1;  
  118.         if(!k)  
  119.             return;  
  120.         split(root,l-1,root,x);  
  121.         split(x,r-l+1,x,y);  
  122.         split(x,r-l+1-k,p,q);  
  123.         merge(x,q,p);  
  124.         merge(x,x,y);  
  125.         merge(root,root,x);  
  126.     }  
  127.     void insert(int k,int v) {  
  128.         int x,y;  
  129.         new_node(x,v);  
  130.         split(root,k,root,y);  
  131.         merge(root,root,x);  
  132.         merge(root,root,y);  
  133.     }  
  134.     void erase(int k) {  
  135.         int x,y;  
  136.         split(root,k-1,root,x);  
  137.         split(x,1,x,y);  
  138.         merge(root,root,y);  
  139.     }  
  140.     int query(int l,int r) {  
  141.         int x,y,ret;  
  142.         split(root,l-1,root,x);  
  143.         split(x,r-l+1,x,y);  
  144.         ret=vmin[x];  
  145.         merge(x,x,y);  
  146.         merge(root,root,x);  
  147.         return ret;  
  148.     }  
  149. } treap;  
  150. int main() {  
  151.     int n,m,x,y,v;  
  152.     char op[10];  
  153.     while(~scanf("%d",&n)) {  
  154.         treap.init();  
  155.         for(int i=1; i<=n; ++i)  
  156.             scanf("%d",&num[i]);  
  157.         treap.build(treap.root,1,n);  
  158.         scanf("%d",&m);  
  159.         while(m--) {  
  160.             scanf("%s",op);  
  161.             switch(op[0]) {  
  162.             case 'A':  
  163.                 scanf("%d%d%d",&x,&y,&v);  
  164.                 treap.plus(x,y,v);  
  165.                 break;  
  166.             case 'R':  
  167.                 scanf("%d%d",&x,&y);  
  168.                 if(op[3]=='E')  
  169.                     treap.reverse(x,y);  
  170.                 else {  
  171.                     scanf("%d",&v);  
  172.                     treap.revolve(x,y,v);  
  173.                 }  
  174.                 break;  
  175.             case 'I':  
  176.                 scanf("%d%d",&x,&v);  
  177.                 treap.insert(x,v);  
  178.                 break;  
  179.             case 'D':  
  180.                 scanf("%d",&x);  
  181.                 treap.erase(x);  
  182.                 break;  
  183.             case 'M':  
  184.                 scanf("%d%d",&x,&y);  
  185.                 printf("%d\n",treap.query(x,y));  
  186.                 break;  
  187.             }  
  188.         }  
  189.     }  
  190. }  

  可持久化Treap

  在拥有了不基于旋转的Treap之后,我们便有了可持久化的Treap。但Treap与其它平衡树不同的地方在于它利用随机权值维护平衡性,考虑一个不断复制子区间的情况,我们发现会出现大量权值相同的节点,从而很容易导致Treap失衡。面对这种情况,范浩强在这篇Blog里选择了用相似方式重写不基于旋转的AVL并可持久化的方式来规避。而在陈立杰的论文中则提到另一种方法,他注意到Treap的随机权值仅仅在merge时利用,于是直接弃掉随机权值,选择在merge时随机概率;在论文中陈立杰表示他无法证明复杂度但找不到可以卡住的数据,在吕凯风(VFleaKing)的群里也讨论过这个问题,当时郭晓旭(ftiasch)说他也无法证明,但实际表现一直良好所以可以直接用。以下是UVa12538的可持久化Treap实现。

  1. #include<cstdio>  
  2. #include<cstdlib>  
  3. #include<cstring>  
  4. using namespace std;  
  5. const int MAXN=50005;  
  6. const int MAXM=5000005;  
  7. int root[MAXN],vs,d;  
  8. struct Treap {  
  9.     int tot;  
  10.     int ch[MAXM][2],size[MAXM];  
  11.     char key[MAXM];  
  12.     bool hey(int x,int y) {  
  13.         return (long long)rand()*(size[x]+size[y])<(long long)size[x]*RAND_MAX;  
  14.     }  
  15.     void init() {  
  16.         tot=0;  
  17.     }  
  18.     void new_node(int &x,char v) {  
  19.         x=++tot;  
  20.         ch[x][0]=ch[x][1]=0;  
  21.         size[x]=1;  
  22.         key[x]=v;  
  23.     }  
  24.     void copy_node(int &x,int y) {  
  25.         if(!y) {  
  26.             x=0;  
  27.             return;  
  28.         }  
  29.         x=++tot;  
  30.         ch[x][0]=ch[y][0];  
  31.         ch[x][1]=ch[y][1];  
  32.         size[x]=size[y];  
  33.         key[x]=key[y];  
  34.     }  
  35.     void merge(int &p,int x,int y) {  
  36.         if(!x||!y) {  
  37.             p=0;  
  38.             if(x)  
  39.                 copy_node(p,x);  
  40.             if(y)  
  41.                 copy_node(p,y);  
  42.             return;  
  43.         }  
  44.         if(hey(x,y)) {  
  45.             copy_node(p,x);  
  46.             merge(ch[p][1],ch[x][1],y);  
  47.         } else {  
  48.             copy_node(p,y);  
  49.             merge(ch[p][0],x,ch[y][0]);  
  50.         }  
  51.         push_up(p);  
  52.     }  
  53.     void split(int p,int sz,int &x,int &y) {  
  54.         if(!sz) {  
  55.             x=0;  
  56.             copy_node(y,p);  
  57.             return;  
  58.         }  
  59.         if(size[ch[p][0]]>=sz) {  
  60.             copy_node(y,p);  
  61.             split(ch[p][0],sz,x,ch[y][0]);  
  62.             push_up(y);  
  63.         } else {  
  64.             copy_node(x,p);  
  65.             split(ch[p][1],sz-size[ch[p][0]]-1,ch[x][1],y);  
  66.             push_up(x);  
  67.         }  
  68.     }  
  69.     void push_up(int x) {  
  70.         size[x]=1;  
  71.         if(ch[x][0])  
  72.             size[x]+=size[ch[x][0]];  
  73.         if(ch[x][1])  
  74.             size[x]+=size[ch[x][1]];  
  75.     }  
  76.     void build(char str[],int &x,int l,int r) {  
  77.         int m=l+r>>1;  
  78.         new_node(x,str[m]);  
  79.         if(l<m)  
  80.             build(str,ch[x][0],l,m-1);  
  81.         if(r>m)  
  82.             build(str,ch[x][1],m+1,r);  
  83.         push_up(x);  
  84.     }  
  85.     void insert(int k,char str[]) {  
  86.         int x,y,z;  
  87.         build(str,x,0,strlen(str)-1);  
  88.         split(root[vs],k,y,z);  
  89.         merge(y,y,x);  
  90.         merge(root[++vs],y,z);  
  91.     }  
  92.     void erase(int k,int sz) {  
  93.         int x,y,z;  
  94.         split(root[vs],k-1,x,y);  
  95.         split(y,sz,y,z);  
  96.         merge(root[++vs],x,z);  
  97.     }  
  98.     void output(int x) {  
  99.         if(ch[x][0])  
  100.             output(ch[x][0]);  
  101.         putchar(key[x]);  
  102.         d+=key[x]=='c';  
  103.         if(ch[x][1])  
  104.             output(ch[x][1]);  
  105.     }  
  106.     void output(int v,int k,int sz) {  
  107.         int x,y,z;  
  108.         split(root[v],k-1,x,y);  
  109.         split(y,sz,y,z);  
  110.         output(y);  
  111.         putchar('\n');  
  112.     }  
  113. } treap;  
  114. int main() {  
  115.     int n,op,p,c,v;  
  116.     char s[105];  
  117.     treap.init();  
  118.     vs=d=0;  
  119.     scanf("%d",&n);  
  120.     while(n--) {  
  121.         scanf("%d",&op);  
  122.         switch(op) {  
  123.         case 1:  
  124.             scanf("%d%s",&p,s);  
  125.             treap.insert(p-d,s);  
  126.             break;  
  127.         case 2:  
  128.             scanf("%d%d",&p,&c);  
  129.             treap.erase(p-d,c-d);  
  130.             break;  
  131.         case 3:  
  132.             scanf("%d%d%d",&v,&p,&c);  
  133.             treap.output(v-d,p-d,c-d);  
  134.             break;  
  135.         }  
  136.     }  
  137. }  

  树链剖分

  树链剖分是非常神奇的东西,常见的轻重链剖分将一棵树划分成至多logn条重链和若干条轻边,满足每个节点属于一条重链,从而将树上路径修改转化为至多logn次线性修改,非常利于套用树状数组、线段树等各类数据结构。树链剖分的常数很小,且因着树链剖分的性质,我们发现越是退化的树(极端情况下成为一条链),树链剖分的效果越是好(极端情况下甚至是O(1)级的,因为只有很少的重链),以至于一些不涉及形态修改的树上路径维护题目,可以用树链剖分套线段树以O(logn^2)的单次操作复杂度水过,且实际表现不输于单次操作O(logn)但常数很大的LCT。常见轻重链剖分的初始化实现是两次dfs的,但dfs有两个问题,一是递归调用使得时间稍慢,二是有些题目有爆栈风险;所以我抄了bfs实现的很好用的交大板。

  需要稍作说明的是,对于点权修改直接维护即可,对于边权修改,常规做法是选定一个根,将边权下垂到深度更大的节点上;换言之,每个点储存的权值是它与它的父节点之间的边权,根节点上没有权值。

  1. int top[MAXN];    //top[p]表示编号为p的路径的顶端节点  
  2. int len[MAXN];    //len[p]表示路径p的长度  
  3. int belong[MAXN]; //belong[v]表示节点v所属的路径编号  
  4. int idx[MAXN];    //idx[v]表示节点v在其路径中的编号,按深度由深到浅依次标号  
  5. int dep[MAXN];    //dep[v]表示节点v的深度  
  6. int fa[MAXN];     //fa[v]表示节点v的父亲节点  
  7. int size[MAXN];   //size[v]表示以节点v为根的子树的节点个数  
  8. int que[MAXN];  
  9. bool vis[MAXN];  
  10. int n,cnt;        //n是点数,标号从1到n  
  11. void split() {  
  12.     memset(dep,0xff,sizeof(dep));  
  13.     int l=0,r=0;  
  14.     que[++r]=1;  
  15.     dep[1]=0;  
  16.     fa[1]=-1;  
  17.     while(l<r) {  
  18.         int u=que[++l];  
  19.         vis[u]=false;  
  20.         for(int i=g.head[u]; ~i; i=g.next[i]) {  
  21.             int v=g.to[i];  
  22.             if(!~dep[v]) {  
  23.                 que[++r]=v;  
  24.                 dep[v]=dep[u]+1;  
  25.                 fa[v]=u;  
  26.             }  
  27.         }  
  28.     }  
  29.     cnt=0;  
  30.     for(int i=n; i>0; --i) {  
  31.         int u=que[i],p=-1;  
  32.         size[u]=1;  
  33.         for(int j=g.head[u]; ~j; j=g.next[j]) {  
  34.             int v=g.to[j];  
  35.             if(vis[v]) {  
  36.                 size[u]+=size[v];  
  37.                 if(!~p||size[v]>size[p])  
  38.                     p=v;  
  39.             }  
  40.         }  
  41.         if(!~p) {  
  42.             idx[u]=len[++cnt]=1;  
  43.             belong[u]=cnt;  
  44.             top[cnt]=u;  
  45.         } else {  
  46.             belong[u]=belong[p];  
  47.             idx[u]=++len[belong[u]];  
  48.             top[belong[u]]=u;  
  49.         }  
  50.         vis[u]=true;  
  51.     }  
  52. }  
  53. int fi[MAXN],cid[MAXN],rank[MAXN];  
  54. void getcid() {  
  55.     fi[1]=1;  
  56.     for(int i=2; i<=cnt; ++i)  
  57.         fi[i]=fi[i-1]+len[i-1];  
  58.     for(int i=1; i<=n; ++i) {  
  59.         cid[i]=fi[belong[i]]+len[belong[i]]-idx[i];  
  60.         rank[cid[i]]=i;  
  61.     }  
  62. }  
  63. // 路径修改和查询依下面修改  
  64. int query(int x,int y) {  
  65.     int ret=0;  
  66.     while(belong[x]!=belong[y]) {  
  67.         if(dep[top[belong[x]]]<dep[top[belong[y]]])  
  68.             swap(x,y);  
  69.         ret=max(ret,query(cid[top[belong[x]]],cid[x],1,n,1));  
  70.         x=fa[top[belong[x]]];  
  71.     }  
  72.     if(dep[x]>dep[y])  
  73.         swap(x,y);  
  74.     ret=max(ret,query(cid[x],cid[y],1,n,1));  
  75.     /*边权如下 
  76.     if(x!=y) 
  77.         ret=max(ret,query(cid[x]+1,cid[y],1,n,1)); 
  78.     */  
  79.     return ret;  
  80. }  

  但dfs实现的树链剖分就没有实际意义了么?非也。dfs实现有一些bfs实现做不到的神奇用法。比如,我们观察第一次dfs,可以发现这和倍增LCA的dfs部分几乎一致;所以稍作修改就可以无缝衔接LCA。更重要的是,我们观察第二次dfs,这次dfs对节点的新位置进行了标号(对应bfs的getcid函数),我们可以发现无论它以怎样的顺序进行dfs(先dfs重儿子再dfs其它子节点),得到的依旧是这棵树的一个dfs序。换句话说,这里处理出的剖分标号,同时也是dfs序标号。我们知道每棵子树的节点在dfs序中都是连续的一段,这样我们就可以同时维护树上路径信息(剖分部分复杂度O(logn))和子树信息(剖分部分复杂度O(1))了。以下是BZOJ3083的树链剖分套线段树实现,同时展现了dfs实现套用LCA和维护链上以及子树信息的做法,非常巧妙;bfs剖分和LCT无法处理这道题的子树询问(有论文阐述了LCT处理子树信息的做法,蒟蒻表示不会)。

  1. #include<cstdio>  
  2. #include<cstring>  
  3. #include<algorithm>  
  4. using namespace std;  
  5. const int MAXN=100005;  
  6. const int maxd=18;  
  7. const int INF=0x7fffffff;  
  8. struct graph {  
  9.     int head[MAXN],tot;  
  10.     int to[MAXN<<1],next[MAXN<<1];  
  11.     void init() {  
  12.         tot=0;  
  13.         memset(head,0xff,sizeof(head));  
  14.     }  
  15.     void add(int u,int v) {  
  16.         to[tot]=v;  
  17.         next[tot]=head[u];  
  18.         head[u]=tot++;  
  19.     }  
  20. } g;  
  21. int top[MAXN],son[MAXN];  
  22. int dep[MAXN],fa[MAXN][maxd],size[MAXN];  
  23. int cid[MAXN],rank[MAXN],cnt;  
  24. void dfs1(int u) {  
  25.     size[u]=1;  
  26.     son[u]=-1;  
  27.     for(int i=1; i<maxd; ++i)  
  28.         fa[u][i]=fa[fa[u][i-1]][i-1];  
  29.     for(int i=g.head[u]; ~i; i=g.next[i]) {  
  30.         int v=g.to[i];  
  31.         if(v!=fa[u][0]) {  
  32.             dep[v]=dep[u]+1;  
  33.             fa[v][0]=u;  
  34.             dfs1(v);  
  35.             size[u]+=size[v];  
  36.             if(!~son[u]||size[v]>size[son[u]])  
  37.                 son[u]=v;  
  38.         }  
  39.     }  
  40. }  
  41. void dfs2(int u,int tp) {  
  42.     top[u]=tp;  
  43.     cid[u]=++cnt;  
  44.     rank[cid[u]]=u;  
  45.     if(~son[u])  
  46.         dfs2(son[u],tp);  
  47.     for(int i=g.head[u]; ~i; i=g.next[i]) {  
  48.         int v=g.to[i];  
  49.         if(v!=son[u]&&v!=fa[u][0])  
  50.             dfs2(v,v);  
  51.     }  
  52. }  
  53. void split() {  
  54.     dfs1(1);  
  55.     cnt=0;  
  56.     dfs2(1,1);  
  57. }  
  58. int lca(int u,int v) {  
  59.     if(dep[u]<dep[v])  
  60.         swap(u,v);  
  61.     int k=dep[u]-dep[v];  
  62.     for(int i=0; i<maxd; ++i)  
  63.         if((1<<i)&k)  
  64.             u=fa[u][i];  
  65.     if(u==v)  
  66.         return u;  
  67.     for(int i=maxd-1; i>=0; --i)  
  68.         if(fa[u][i]!=fa[v][i]) {  
  69.             u=fa[u][i];  
  70.             v=fa[v][i];  
  71.         }  
  72.     return fa[u][0];  
  73. }  
  74. int n,root;  
  75. int a[MAXN];  
  76. #define lson l,m,rt<<1  
  77. #define rson m+1,r,rt<<1|1  
  78. int vmin[MAXN<<2],col[MAXN<<2];  
  79. void push_up(int rt) {  
  80.     vmin[rt]=min(vmin[rt<<1],vmin[rt<<1|1]);  
  81. }  
  82. void push_down(int rt) {  
  83.     if(col[rt]) {  
  84.         col[rt<<1]=col[rt<<1|1]=vmin[rt<<1]=vmin[rt<<1|1]=col[rt];  
  85.         col[rt]=0;  
  86.     }  
  87. }  
  88. void build(int l,int r,int rt) {  
  89.     col[rt]=0;  
  90.     if(l==r) {  
  91.         vmin[rt]=a[rank[l]];  
  92.         return;  
  93.     }  
  94.     int m=l+r>>1;  
  95.     build(lson);  
  96.     build(rson);  
  97.     push_up(rt);  
  98. }  
  99. void update(int L,int R,int val,int l,int r,int rt) {  
  100.     if(L<=l&&r<=R) {  
  101.         col[rt]=vmin[rt]=val;  
  102.         return;  
  103.     }  
  104.     push_down(rt);  
  105.     int m=l+r>>1;  
  106.     if(L<=m)  
  107.         update(L,R,val,lson);  
  108.     if(m<R)  
  109.         update(L,R,val,rson);  
  110.     push_up(rt);  
  111. }  
  112. int query(int L,int R,int l,int r,int rt) {  
  113.     if(L<=l&&r<=R)  
  114.         return vmin[rt];  
  115.     push_down(rt);  
  116.     int m=l+r>>1;  
  117.     int ret=INF;  
  118.     if(L<=m)  
  119.         ret=min(ret,query(L,R,lson));  
  120.     if(m<R)  
  121.         ret=min(ret,query(L,R,rson));  
  122.     return ret;  
  123. }  
  124. void modify(int x,int y,int d) {  
  125.     while(top[x]!=top[y]) {  
  126.         if(dep[top[x]]<dep[top[y]])  
  127.             swap(x,y);  
  128.         update(cid[top[x]],cid[x],d,1,n,1);  
  129.         x=fa[top[x]][0];  
  130.     }  
  131.     if(dep[x]>dep[y])  
  132.         swap(x,y);  
  133.     update(cid[x],cid[y],d,1,n,1);  
  134. }  
  135. int query(int rt) {  
  136.     if(rt==root)  
  137.         return query(1,n,1,n,1);  
  138.     int pre=lca(root,rt);  
  139.     if(pre!=rt)  
  140.         return query(cid[rt],cid[rt]+size[rt]-1,1,n,1);  
  141.     int depth=dep[root]-dep[rt]-1,tmp=root;  
  142.     for(int i=maxd-1; i>=0; --i)  
  143.         if(depth&(1<<i))  
  144.             tmp=fa[tmp][i];  
  145.     return min(query(1,cid[tmp]-1,1,n,1),query(cid[tmp]+size[tmp],n,1,n,1));  
  146. }  
  147. int main() {  
  148.     int m,u,v,opt,id;  
  149.     while(~scanf("%d%d",&n,&m)) {  
  150.         g.init();  
  151.         for(int i=1; i<n; ++i) {  
  152.             scanf("%d%d",&u,&v);  
  153.             g.add(u,v);  
  154.             g.add(v,u);  
  155.         }  
  156.         for(int i=1; i<=n; ++i)  
  157.             scanf("%d",&a[i]);  
  158.         split();  
  159.         build(1,n,1);  
  160.         scanf("%d",&root);  
  161.         while(m--) {  
  162.             scanf("%d",&opt);  
  163.             switch(opt) {  
  164.             case 1:  
  165.                 scanf("%d",&root);  
  166.                 break;  
  167.             case 2:  
  168.                 scanf("%d%d%d",&u,&v,&id);  
  169.                 modify(u,v,id);  
  170.                 break;  
  171.             case 3:  
  172.                 scanf("%d",&id);  
  173.                 printf("%d\n",query(id));  
  174.                 break;  
  175.             }  
  176.         }  
  177.     }  
  178. }  

  KD-Tree

  KD-Tree并不是一个在竞赛中常见的数据结构,相反在需要处理多维数据的场合有很多的实际应用;在ACM中主要用来维护多维第K近点对距离一类的信息。它的主要思想是在每一维上依次用一个超平面进行空间划分,将点集比较均匀地分割在各个区域内,结构上则是一棵二叉树,且与线段树的形态和构造方法都有些类似。经过改造的KD-Tree一般可以做到O(logn)的单点插入,以及O(n^(1-1/D))的询问操作,其中D是维数;可见维数越大KD-Tree越慢;实质上KD-Tree本身也并不是一个速度很快的数据结构,可以说只为解决特定问题而生。询问距离一个点的前K近点一般需要用一个优先队列进行询问时的维护,写起来其实很简单。以下是HDU4347的代码。

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. #include<queue>  
  4. using namespace std;  
  5. const int MAXN=50005;  
  6. const int INF=~0U>>1;  
  7. const int DIM=5;  
  8. #define lson l,m-1,dep+1  
  9. #define rson m+1,r,dep+1  
  10. int cur,K;  
  11. struct point {  
  12.     int x[DIM];  
  13.     bool operator<(const point &oth) const {  
  14.         return x[cur]<oth.x[cur];  
  15.     }  
  16.     void output() {  
  17.         for(int i=0; i<K; ++i)  
  18.             printf("%d%c",x[i],i<K-1?' ':'\n');  
  19.     }  
  20. } vec[MAXN],origin[MAXN],pt,ans[10];  
  21. inline int sqr(int x) {  
  22.     return x*x;  
  23. }  
  24. int dist(const point &a,const point &b) {  
  25.     int ret=0;  
  26.     for(int i=0; i<K; ++i)  
  27.         ret+=sqr(a.x[i]-b.x[i]);  
  28.     return ret;  
  29. }  
  30. void build(int l,int r,int dep=0) {  
  31.     if(l>=r)  
  32.         return;  
  33.     int m=l+r>>1;  
  34.     cur=dep%K;  
  35.     nth_element(vec+l,vec+m,vec+r+1);  
  36.     build(lson);  
  37.     build(rson);  
  38. }  
  39. priority_queue<pair<int,point> > pq;  
  40. void query(const point &x,int k,int l,int r,int dep=0) {  
  41.     if(l>r)  
  42.         return;  
  43.     int m=l+r>>1,cur=dep%K;  
  44.     pair<int,point> tmp(dist(x,vec[m]),vec[m]);  
  45.     if(pq.size()<k)  
  46.         pq.push(tmp);  
  47.     else if(pq.top().first>tmp.first) {  
  48.         pq.pop();  
  49.         pq.push(tmp);  
  50.     }  
  51.     if(x.x[cur]<vec[m].x[cur]) {  
  52.         query(x,k,lson);  
  53.         if(pq.top().first>sqr(x.x[cur]-vec[m].x[cur]))  
  54.             query(x,k,rson);  
  55.     } else {  
  56.         query(x,k,rson);  
  57.         if(pq.top().first>sqr(x.x[cur]-vec[m].x[cur]))  
  58.             query(x,k,lson);  
  59.     }  
  60. }  
  61. int main() {  
  62.     int n,t,m;  
  63.     while(~scanf("%d%d",&n,&K)) {  
  64.         for(int i=1; i<=n; ++i) {  
  65.             for(int j=0; j<K; ++j)  
  66.                 scanf("%d",&origin[i].x[j]);  
  67.             vec[i]=origin[i];  
  68.         }  
  69.         build(1,n);  
  70.         scanf("%d",&t);  
  71.         while(t--) {  
  72.             for(int i=0; i<K; ++i)  
  73.                 scanf("%d",&pt.x[i]);  
  74.             scanf("%d",&m);  
  75.             query(pt,m,1,n);  
  76.             for(int i=0; i<m; ++i) {  
  77.                 ans[i]=pq.top().second;  
  78.                 pq.pop();  
  79.             }  
  80.             printf("the closest %d points are:\n", m);  
  81.             for(int i=m-1; i>=0; --i)  
  82.                 ans[i].output();  
  83.         }  
  84.     }  
  85. }  

  以上代码的预处理复杂度是O(n),但并不支持点的插入和删除。由此我找到另外一份风格迥异的模板,较好地解决了这个问题。插入其实和平衡树有些类似,而删除则是通过删除标记完成。

  1. #define lson kdt[rt].ls,dep+1  
  2. #define rson kdt[rt].rs,dep+1  
  3. struct kdnode {  
  4.     int ls,rs,x[DIM];  
  5.     bool flag; //删点标记  
  6. } kdt[MAXN];  
  7. inline long long sqr(int x) {  
  8.     return (long long)x*x;  
  9. }  
  10. long long dist(const kdnode &a,const kdnode &b) {  
  11.     long long ret=0;  
  12.     for(int i=0; i<DIM; ++i)  
  13.         ret+=sqr(a.x[i]-b.x[i]);  
  14.     return ret;  
  15. }  
  16. int root,tot;  
  17. void init() {  
  18.     tot=0;  
  19.     root=-1;  
  20. }  
  21. int add(int pt[]) {  
  22.     kdt[tot].flag=false;  
  23.     kdt[tot].ls=kdt[tot].rs=-1;  
  24.     for(int i=0; i<DIM; ++i)  
  25.         kdt[tot].x[i]=pt[i];  
  26.     return tot++;  
  27. }  
  28. void insert(int pt[],int rt,int dep=0) {  
  29.     dep%=DIM;  
  30.     if(pt[dep]<kdt[rt].x[dep]) {  
  31.         if(!~kdt[rt].ls)  
  32.             kdt[rt].ls=add(pt);  
  33.         else  
  34.             insert(pt,lson);  
  35.     } else {  
  36.         if(!~kdt[rt].rs)  
  37.             kdt[rt].rs=add(pt);  
  38.         else  
  39.             insert(pt,rson);  
  40.     }  
  41. }  
  42. //求最近点距离  
  43. long long query(const kdnode &pt,int rt,int dep=0) {  
  44.     if(!~rt)  
  45.         return INF;  
  46.     dep%=DIM;  
  47.     long long ret=INF,tmp=sqr(kdt[rt].x[dep]-pt.x[dep]);  
  48.     if(!kdt[rt].flag)  
  49.         ret=dist(kdt[rt],pt);  
  50.     if(pt.x[dep]<=kdt[rt].x[dep]) {  
  51.         ret=min(ret,query(pt,lson));  
  52.         if(tmp<ret)  
  53.             ret=min(ret,query(pt,rson));  
  54.     }  
  55.     if(pt.x[dep]>=kdt[rt].x[dep]) {  
  56.         ret=min(ret,query(pt,rson));  
  57.         if(tmp<ret)  
  58.             ret=min(ret,query(pt,lson));  
  59.     }  
  60.     return ret;  
  61. }  
  62. //查询区间内有多少个点  
  63. int query(int pt1[],int pt2[],int rt,int dep=0) {  
  64.     if(!~rt)  
  65.         return 0;  
  66.     dep%=DIM;  
  67.     int ret=0,cur;  
  68.     for(cur=0; cur<DIM; ++cur)  
  69.         if(kdt[rt].x[cur]<pt1[cur]||kdt[rt].x[cur]>pt2[cur])  
  70.             break;  
  71.     if(cur==DIM)  
  72.         ++ret;  
  73.     if(pt2[dep]<kdt[rt].x[dep])  
  74.         ret+=query(pt1,pt2,lson);  
  75.     else if(pt1[dep]>=kdt[rt].x[dep])  
  76.         ret+=query(pt1,pt2,rson);  
  77.     else {  
  78.         ret+=query(pt1,pt2,lson);  
  79.         ret+=query(pt1,pt2,rson);  
  80.     }  
  81.     return ret;  
  82. }  

  划分树

  划分树也是基于线段树的一种数据结构,通常用于处理无修改询问区间第k小值。它的建树过程与快排类似,基本上相当于将快排的过程用一棵树存储下来。划分树的代码不算长,但并不是很好写;一开始我以为可持久化权值线段树完全可以代替划分树(好写,速度快,只是内存占用稍大),但HDU3473教我做人= =因为这道题需要维护的信息在可持久化权值线段树下有点麻烦,至少不够直观(也可能是我比较弱的缘故)。但除此之外,划分树也没有更多的扩展性应用了。

  1. #define lson l,m,dep+1  
  2. #define rson m+1,r,dep+1  
  3. int part[20][MAXN]; //表示每层每个位置的值  
  4. int sod[MAXN]; //已经排序好的数  
  5. int tol[20][MAXN]; //tol[p][i] 表示第i层从1到i有数分入左边  
  6. void build(int l,int r,int dep) {  
  7.     if(l==r)  
  8.         return;  
  9.     int m=l+r>>1,cnt=m-l+1; //表示等于中间值而且被分入左边的个数  
  10.     for(int i=l; i<=r; ++i)  
  11.         if(part[dep][i]<sod[m])  
  12.             --cnt;  
  13.     int lpos=l,rpos=m+1;  
  14.     for(int i=l; i<=r; ++i) {  
  15.         if(part[dep][i]<sod[m])  
  16.             part[dep+1][lpos++]=part[dep][i];  
  17.         else if(part[dep][i]==sod[m]&&cnt>0) {  
  18.             part[dep+1][lpos++]=part[dep][i];  
  19.             --cnt;  
  20.         } else  
  21.             part[dep+1][rpos++]=part[dep][i];  
  22.         tol[dep][i]=tol[dep][l-1]+lpos-l;  
  23.     }  
  24.     build(lson);  
  25.     build(rson);  
  26. }  
  27. //查询区间第k大的数  
  28. int query(int L,int R,int k,int l,int r,int dep) {  
  29.     if(L==R)  
  30.         return part[dep][L];  
  31.     int m=l+r>>1,cnt=tol[dep][R]-tol[dep][L-1];  
  32.     if(cnt>=k) {  
  33.         int tl=l+tol[dep][L-1]-tol[dep][l-1],tr=tl+cnt-1;  
  34.         return query(tl,tr,k,lson);  
  35.     } else {  
  36.         int tr=R+tol[dep][r]-tol[dep][R],tl=tr-(R-L-cnt);  
  37.         return query(tl,tr,k-cnt,rson);  
  38.     }  
  39. }  

  左偏树

  左偏树也不是一个很常用的数据结构,它其实是可并堆的一种实现,可以在O(logn)的时间内实现堆的push、pop和两个堆的合并操作,以及O(1)时间的取堆顶操作。不常见的原因是竞赛中很少遇到需要将堆进行合并的需求,而在掌握了Treap、Splay等平衡树之后,也可以用启发式合并的办法实现O(logn^2)甚至O(logn)(Fotile说Splay按某种遍历序合并可以达到)的合并复杂度。但左偏树的编程复杂度很小,常数也不错;它常常搭配并查集使用(而且也很好搭配在一起)。以下是HDU1512的左偏树+并查集做法。

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4. const int MAXN=100005;  
  5. int val[MAXN],ls[MAXN],rs[MAXN],dep[MAXN],fa[MAXN];  
  6. void init(int n) {  
  7.     for(int i=1; i<=n; ++i) {  
  8.         scanf("%d",&val[i]);  
  9.         ls[i]=rs[i]=dep[i]=0;  
  10.         fa[i]=i;  
  11.     }  
  12. }  
  13. int find(int x) {  
  14.     if(fa[x]!=x)  
  15.         fa[x]=find(fa[x]);  
  16.     return fa[x];  
  17. }  
  18. int merge(int x,int y) {  
  19.     if(!x||!y)  
  20.         return x|y;  
  21.     if(val[x]<val[y])  
  22.         swap(x,y);  
  23.     rs[x]=merge(rs[x],y);  
  24.     fa[rs[x]]=x;  
  25.     if(dep[ls[x]]<dep[rs[x]])  
  26.         swap(ls[x],rs[x]);  
  27.     dep[x]=dep[rs[x]]+1;  
  28.     return x;  
  29. }  
  30. int push(int x,int y) {  
  31.     return merge(x,y);  
  32. }  
  33. int pop(int x) {  
  34.     int a=ls[x],b=rs[x];  
  35.     ls[x]=rs[x]=dep[x]=0;  
  36.     fa[x]=x;  
  37.     fa[a]=a;  
  38.     fa[b]=b;  
  39.     return merge(a,b);  
  40. }  
  41. int main() {  
  42.     int n,m,x,y;  
  43.     while(~scanf("%d",&n)) {  
  44.         init(n);  
  45.         scanf("%d",&m);  
  46.         while(m--) {  
  47.             scanf("%d%d",&x,&y);  
  48.             int a=find(x),b=find(y);  
  49.             if(a==b)  
  50.                 puts("-1");  
  51.             else {  
  52.                 val[a]>>=1;  
  53.                 val[b]>>=1;  
  54.                 a=push(pop(a),a);  
  55.                 b=push(pop(b),b);  
  56.                 printf("%d\n",val[merge(a,b)]);  
  57.             }  
  58.         }  
  59.     }  
  60. }  

  笛卡尔树

  也是一个不太有名的数据结构,但其实很“常见”。考虑一个键值对的序列,当键与键,值与值之间互不相同时,它们可以唯一地构成这样一棵二叉树:key在中序遍历时呈升序,满足二叉查找树性质;父节点的value大于子节点的value,满足堆的性质。于是我们发现,Treap恰好是将value随机从而保证节点期望深度的笛卡尔树。一个键值对序列的笛卡儿树可以O(n)时间内构造出来,有一个巧妙的应用是POJ2559HDU1506,虽然一般做法是用单调栈来搞。以下是POJ2201(裸笛卡儿树构造题)的实现。

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4. const int MAXN=50005;  
  5. int idx[MAXN],n;  
  6. struct Cartesian_Tree {  
  7.     int root,key[MAXN],val[MAXN],ch[MAXN][2],pre[MAXN];  
  8.     void init() {  
  9.         for(int i=1; i<=n; ++i) {  
  10.             scanf("%d%d",&key[i],&val[i]);  
  11.             ch[i][0]=ch[i][1]=pre[i]=0;  
  12.         }  
  13.     }  
  14.     void build() {  
  15.         static int st[MAXN];  
  16.         int top=-1;  
  17.         for(int i=1; i<=n; ++i) {  
  18.             int k=top;  
  19.             while(k>=0&&val[st[k]]>val[idx[i]])  
  20.                 --k;  
  21.             if(~k) {  
  22.                 pre[idx[i]]=st[k];  
  23.                 ch[st[k]][1]=idx[i];  
  24.             }  
  25.             if(k<top) {  
  26.                 pre[st[k+1]]=idx[i];  
  27.                 ch[idx[i]][0]=st[k+1];  
  28.             }  
  29.             st[++k]=idx[i];  
  30.             top=k;  
  31.         }  
  32.         root=st[0];  
  33.     }  
  34. } ct;  
  35. bool cmp(int x,int y) {  
  36.     return ct.key[x]<ct.key[y];  
  37. }  
  38. int main() {  
  39.     while(~scanf("%d",&n)) {  
  40.         ct.init();  
  41.         for(int i=1; i<=n; ++i)  
  42.             idx[i]=i;  
  43.         sort(idx+1,idx+n+1,cmp);  
  44.         ct.build();  
  45.         puts("YES");  
  46.         for(int i=1; i<=n; ++i)  
  47.             printf("%d %d %d\n",ct.pre[i],ct.ch[i][0],ct.ch[i][1]);  
  48.     }  
  49. }  

  基本上在ACM竞赛中常见的数据结构就这些了,这里的数据结构大都用于处理一些通用数据,而对于一些特定数据的没有涉及,比如处理字符串的Trie树、后缀数组、自动机等。其余的一些数据结构在学习过程中会酌情更新。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值