[关闭]
@dxbdly 2021-12-26T07:24:27.000000Z 字数 4404 阅读 183

G2020 19寒假集训Day5

信息学——19寒假集训


今天是G2020寒假集训Day5,主要讲数据结构中的主席树平衡树

前置芝士点:线段树,BST树

PS:此文章默认已学线段树,BST树等简单的数据结构。

一. 主席树

1.定义

主席树,又称可持久化线段树,线段树大家都不陌生,那么什么是可持久化呢?

可持久化:可以支持回退,访问之前历史版本的数据结构

所以说主席树就是:

可以访问历史版本的线段树!

2.主席树的思想

先来看题:题面

我们会发现,此题要求在历史版本上查询或修改,首先很容易想到一种解法:

暴力

开个二维数组,将每个历史版本都存下来,操作时直接访问历史版本即可,空间复杂度,时间复杂度

显然不行

暴力优化

我们在暴力上进行优化,将二维数组改成M个链表。

我们容易发现在修改下标之后当前版本与历史版本完全相同

所以,我们可以将修改元素之前的

修改元素之后的直接将指针指向历史版本即可。

但此算法仍可以被卡掉,如果我的修改永远在最后一个,时间复杂度仍为

主席树

对上面的思想进行分析,我们会发现:

由于此题只需要单点修改,单点查询。

如果对应到一颗线段树上,那么修改与查询均只与叶节点有关

我们思考能不能引用前面的优化思想,将其运用到线段树上?

于是,可持久化线段树(主席树)诞生了!

主席树的思想:

对于不包含需修改节点的子树,我们直接用指针指向历史版本。

对于包含需修改节点的子树,我们暴力

又因为对于线段树每一层,有且仅有一个节点子树包含需修改节点,而最多只有

所以最多 次,单次时间复杂度

成功解决!

主席树的实现

注意:

1.主席树需要动态开点,所以需要存下每个节点左,右儿子。

2.实现时并不需要设一个指针指向历史版本
而是存下了每个版本的根节点,并存下其左,右儿子是哪一个。

建树

建树操作与线段树基本相同

代码:

  1. //The code is from dxbdly
  2. inline void build(int &i,int l,int r)//注意i需要随操作变化
  3. {
  4. i=++tot;
  5. if (l==r)
  6. {
  7. tree[i].num=a[l];
  8. return ;
  9. }
  10. int mid=(l+r)>>1;
  11. build(tree[i].l,l,mid);
  12. build(tree[i].r,mid+1,r);
  13. }

修改

修改操作最重要的是确定哪些子树包含需要修改的节点。

我们可以考虑线段树的修改,由于是单点修改,所以每次修改时所遍历到的节点一定都包含待修节点,将其 即可

代码:

  1. inline void update(int &i,int v,int l,int r,int x,int val)
  2. {
  3. copy(i,v);
  4. if (l==r)
  5. {
  6. tree[i].num=val;
  7. return ;
  8. }
  9. int mid=(l+r)>>1;
  10. if (x<=mid)
  11. update(tree[i].l,tree[v].l,l,mid,x,val);
  12. else
  13. update(tree[i].r,tree[v].r,mid+1,r,x,val);
  14. }

查询

查询操作也比较简单,确定历史版本所在根节点,直接找到所在历史版本的根节点,然后线段树查询即可。

  1. inline int query(int i,int l,int r,int x)
  2. {
  3. if (l==r)
  4. return tree[i].num;
  5. int mid=(l+r)>>1;
  6. if (x<=mid)
  7. return query(tree[i].l,l,mid,x);
  8. else
  9. return query(tree[i].r,mid+1,r,x);
  10. }

总时间复杂度

AC总代码

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n,m;
  16. struct node{
  17. int l,r,num;
  18. }tree[20000005];
  19. int a[20000005],tot;
  20. int root[20000005];
  21. inline void copy(int &i,int v)
  22. {
  23. i=++tot,tree[i]=tree[v];
  24. }
  25. inline void build(int &i,int l,int r)
  26. {
  27. i=++tot;
  28. if (l==r)
  29. {
  30. tree[i].num=a[l];
  31. return ;
  32. }
  33. int mid=(l+r)>>1;
  34. build(tree[i].l,l,mid);
  35. build(tree[i].r,mid+1,r);
  36. }
  37. inline void update(int &i,int v,int l,int r,int x,int val)
  38. {
  39. copy(i,v);
  40. if (l==r)
  41. {
  42. tree[i].num=val;
  43. return ;
  44. }
  45. int mid=(l+r)>>1;
  46. if (x<=mid)
  47. update(tree[i].l,tree[v].l,l,mid,x,val);
  48. else
  49. update(tree[i].r,tree[v].r,mid+1,r,x,val);
  50. }
  51. inline int query(int i,int l,int r,int x)
  52. {
  53. if (l==r)
  54. return tree[i].num;
  55. int mid=(l+r)>>1;
  56. if (x<=mid)
  57. return query(tree[i].l,l,mid,x);
  58. else
  59. return query(tree[i].r,mid+1,r,x);
  60. }
  61. int main(){
  62. n=read(),m=read();
  63. for (register int i=1;i<=n;++i)
  64. a[i]=read();
  65. build(root[0],1,n);
  66. for (register int i=1;i<=m;++i)
  67. {
  68. int v=read(),op=read(),x,val;
  69. if (op==1)
  70. x=read(),val=read(),update(root[i],root[v],1,n,x,val);
  71. else
  72. x=read(),printf("%d\n",query(root[v],1,n,x)),root[i]=root[v];
  73. }
  74. return 0;
  75. }

4.主席树的应用

来讲一下主席树的经典应用:区间第

题面

题解:

首先不管这个区间的第小如何求,我们先来想区间的第小如何求。

其实将上面说的主席树做一些改变即可:

将线段树改为权值线段树,对于每个[1,i]的区间建一个历史版本

将线段树改为权值线段树的话,我们可以这样操作。

查询时,对于当前点记他左儿子的个数为

若::则说明第小一定在左子树中
若::则说明第小一定在右子树中

对于第一种, 不变,递归左子树处理。
对于第二种, 变为 ,递归右子树处理。

那么我们就解决了区间的第

对于区间的第小,由于我们维护的是n个权值线段树

所以我们可以在查询时可以通过区间减法

的权值线段树的权值线段树,即可得出的权值线段树。

而,权值线段树相减,只需要各个数字对应相减即可。

注意:数字过大,需要离散化。

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n,m,len;
  16. int a[5000005],ls[5000005],root[5000005];
  17. struct node{
  18. int l,r,sum;
  19. }tree[5000005];
  20. int tot;
  21. inline void copy(int &i,int v)
  22. {
  23. i=++tot,tree[i]=tree[v],tree[i].sum++;
  24. }
  25. inline void build(int &i,int l,int r)
  26. {
  27. i=++tot;
  28. if (l==r)
  29. return ;
  30. int mid=(l+r)>>1;
  31. build(i,l,mid);
  32. build(i,mid+1,r);
  33. }
  34. inline void update(int &i,int v,int l,int r,int x)
  35. {
  36. copy(i,v);
  37. if (l==r)
  38. return ;
  39. int mid=(l+r)>>1;
  40. if (x<=mid)
  41. update(tree[i].l,tree[v].l,l,mid,x);
  42. else
  43. update(tree[i].r,tree[v].r,mid+1,r,x);
  44. }
  45. inline int query(int u,int v,int l,int r,int k)
  46. {
  47. if (l==r)
  48. return l;
  49. int mid=(l+r)>>1,x=tree[tree[v].l].sum-tree[tree[u].l].sum;
  50. if (x>=k)
  51. return query(tree[u].l,tree[v].l,l,mid,k);
  52. else
  53. return query(tree[u].r,tree[v].r,mid+1,r,k-x);
  54. }
  55. int main(){
  56. n=read(),m=read();
  57. for (register int i=1;i<=n;++i)
  58. a[i]=read(),ls[i]=a[i];
  59. sort(ls+1,ls+n+1);
  60. len=unique(ls+1,ls+n+1)-ls-1;
  61. build(root[0],1,len);
  62. for (register int i=1;i<=n;++i)
  63. {
  64. int x=lower_bound(ls+1,ls+len+1,a[i])-ls;
  65. update(root[i],root[i-1],1,len,x);
  66. }
  67. for (register int i=1;i<=m;++i)
  68. {
  69. int l=read(),r=read(),k=read();
  70. printf("%d\n",ls[query(root[l-1],root[r],1,len,k)]);
  71. }
  72. return 0;
  73. }

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注