• 主席树的学习


    前言

    主席树可真是个好东西

    之前一直都觉得挺难的

    今天一看

    woc这么简单!

    怎么可能,我还是太蒟蒻了

    感谢akakw1大佬的指导!


    一.前置知识及算法思路

    1.可持久化

    因为主席树是可持久化线段树,所以还是有必要了解一下可持久化

    可持久化的数据结构是可以支持访问任一历史版本的信息(也就是每一次修改操作之前的情况)

    2.如何实现

    以可持久化线段树为例:

    很自然的,我们可以想到对于每一个版本开一个线段树

    但考虑到这样做的空间复杂度是(O(k*n*4)) (k为修改次数)

    果断放弃

    3.优化

    考虑进行优化.

    盗一张图

    假如我们现在要修改的点是4(也就是区间[4,4])

    可以发现区间[1,3]和区间[5,6]是完全没有改变的,

    因此我们考虑利用上一版本

    具体解释:当我们递归[1,6]的左子树[1,3]时发现4根本没有在其中,这也就意味着当前版本的[1,3]节点与上一版本完全一样!

    因此我们直接让新开的根节点(也就是橙色的[1.6])的左子节点指向原来的根节点(也就是蓝色的[1.6])的左子节点(蓝色的[1,3]);

    接着继续递归右子树[4,6],发现[6,6]也可以直接利用上一版本,以此类推

    然后就发现每一次就只要开(log_2 n)个节点了!

    二.例题

    洛谷 P3834【模板】可持久化线段树 1(主席树)

    题目就是让你查询区间[l,r]的第k小值

    离散化一下值域

    这题开个可持久化值域线段树就可以啦

    代码

     1 #include<bits/stdc++.h>
     2 #define R register int
     3 #define gc getchar
     4 using namespace std;
     5 const int MAX_N=2e5+10,MAX_MLOGN=4e6+10;
     6 int tot,root[MAX_N],n,m,q,a[MAX_N],b[MAX_N];
     7 int rd()
     8 {
     9     int ans=0,flag=1;
    10     char ch=gc();
    11     while((ch<'0'||ch>'9')&&ch!='-')ch=gc();
    12     if(ch=='-')flag=-1,ch=gc();
    13     while(ch>='0'&&ch<='9')ans=ans*10+ch-48,ch=gc();
    14     return ans*flag;
    15 }
    16 struct Segment_Tree{
    17     int l,r;
    18     int dat;
    19 }t[MAX_MLOGN];
    20 
    21 //l,r为左右子节点编号
    22 //dat是当前版本的序列下 x的个数(L_i<=x<=R_i(i为节点编号,这里的L,R*要与结构体中的l,r加以区分))
    23 //*这里的L,R是指当前节点在线段树中所代表的值域[L,R]
    24 
    25 int Build(int l,int r)//l,r都为值域,函数的返回值是节点编号
    26 //调用入口为Build(1,m) *(1,m)为离散后的值域
    27 {
    28     int p=++tot;
    29     t[p].dat=0;
    30     //因为主席树不再是完全二叉树,不满足t[p*2]是t[p]的子节点
    31     //所以直接用tot来记录节点编号
    32     if(l>=r)
    33     {
    34         return p;
    35     }
    36     int mid=(l+r)>>1;
    37     t[p].l=Build(l,mid);
    38     t[p].r=Build(mid+1,r);
    39     return p;
    40 }
    41 int Insert(int pre,int l,int r,int x)//pre是上一版本的同一位置的节点的编号;l,r为值域;x为值
    42 //函数返回值仍然是节点编号
    43 {
    44     int p=++tot;
    45     t[p].l=t[pre].l,t[p].r=t[pre].r;
    46     t[p].dat=t[pre].dat+1;//可以先直接继承上一版本的数据
    47     int mid=(l+r)>>1;
    48     if(l<r)
    49     {
    50         if(x<=mid)//按值域划分,不用多说
    51         {
    52             t[p].l=Insert(t[pre].l,l,mid,x);//这里是递归t[pre]而不是t[p]
    53         }
    54         else
    55         {
    56             t[p].r=Insert(t[pre].r,mid+1,r,x);//同理
    57         }
    58     }
    59     return p;
    60 }
    61 int Query(int u,int v,int l,int r,int k)
    62 {
    63     if(l>=r)return l;
    64     int mid=(l+r)>>1;
    65     int tmp=t[t[v].l].dat-t[t[u].l].dat;
    66     if(tmp<k)return Query(t[u].r,t[v].r,mid+1,r,k-tmp);
    67     else return Query(t[u].l,t[v].l,l,mid,k);
    68 }
    69 int main()
    70 {
    71     n=rd(),q=rd();
    72     for(R i=1;i<=n;i++)
    73     {
    74         b[i]=a[i]=rd();
    75     }
    76     /**/
    77     sort(b+1, b+1+n);
    78     m=unique(b+1,b+1+n)-b-1;//离散化
    79     /**/
    80     root[0]=Build(1,m);
    81     for(R i=1;i<=n;i++)
    82     {
    83         R t=lower_bound(b+1,b+1+m,a[i])-b;
    84         root[i]=Insert(root[i-1],1,m,t);
    85     }
    86     for(R i=1;i<=q;i++)
    87     {
    88         int x=rd(),y=rd(),k=rd();
    89         R tmp=Query(root[x-1],root[y],1,m,k);
    90         printf("%d
    ",b[tmp]);
    91     }
    92     return 0;
    93 }

    我太蒟了所以加了一大波注释(怕自己以后看不懂)

    我太蒟了所以以上文字有什么问题可以在评论区回复

    话说会有人看吗QAQ

  • 相关阅读:
    uniapp爬坑之旅_开发一个自己的app_day27_完善数据库并给添加任务限制条件
    uniapp爬坑之旅_开发一个自己的app_day45_任务左滑不太好搞
    uniapp爬坑之旅_开发一个自己的app_day47_主页面基本完成
    uniapp爬坑之旅_开发一个自己的app_day28_完成任务条信息和数据库的同步
    uniapp爬坑之旅_开发一个自己的app_day31_增加主页面任务条滚动功能
    uniapp爬坑之旅_开发一个自己的app_day35
    uniapp爬坑之旅_开发一个自己的app_day46_实现任务左滑
    uniapp爬坑之旅_开发一个自己的app_day29_增加删除任务功能
    uniapp爬坑之旅_开发一个自己的app_day32_增加修改任务功能
    c++:class,名字空间等
  • 原文地址:https://www.cnblogs.com/Zenyz/p/9873574.html
Copyright © 2020-2023  润新知