主席树的简要讲解

这篇具有很好参考价值的文章主要介绍了主席树的简要讲解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

区间第k小值

主席树是解决动态查找序列上[l,r]区间中的第k小值的一个数据结构

核心思想:动态开点(后面会讲)
传统线段树都是值域线段树
其实意思就是每个节点都存的是序列上[l,r]的一个区间和,但是考虑我们需要动态处理区间的不是最值,故换一种线段树

主席树一般用的是权值线段树
就是把[l,r]改为[min(a),max(a)] a是序列A[l,r]区间里的一个子序列,每个节点下都保存一个统计数字s,代表在当前权值域里,有多少个数字
array : 1 4 3 3

           [1,4]
           s = 4
    [1,2]         [3,4]
    s = 1         s = 3
[1,1]  [2,2]    [3,3] [4,4]
s = 1  s = 0    s = 2  s = 1

 文章来源地址https://www.toymoban.com/news/detail-856447.html

 (不会画图QWQ)

 步入正题
我们转换思想,每一次开点都存储一个版本的权值线段树,但是这样会让空间复杂度直线上升为O(n^2),这是不能容忍的
但是我们再想
这是一颗树性结构,我们每一次加点都只会改变上下log n个点的数据,为什么我们就不能每一次都开一个log n的新点,将与这次更改无关的点直接开一条新边连接到新点呢?

不能像上一个那样形似了,我直接盗图
主席树的简要讲解

这是第一个版本在insert(4)后的结果
每一次insert都存储一个root[i+1],表示第i+1个版本的根节点

 

但是如何查询[l,r]区间内的第k小数呢
这时候就要运用我们前缀和的思想


 [l,r]的信息 = [1,r]的信息 - [1,l-1]的信息
即 S[l,r] = S[1,r] - S[1,l-1]

离散化
这个其实没什么好讲的,就是预处理,详情见代码

 

大概意思是懂了吧,来,让我们步入总结

常规操作
1.插入insert(),动态开点
2.查询[l,r]区间内第k小值
3.离散化

时间复杂度 O(n log^2 n)

luogu P3834
#### C++ 代码

 1 #include"bits/stdc++.h"
 2 
 3 using namespace std;
 4 const int N=200010;
 5 #define inl inline
 6 #define reg register
 7 #define regi register int
 8 #define PII pair<int,int>
 9 //#define ll long long
10 inl int read(void)
11 {
12     int x=0,f=1;char ch=getchar();
13     while(!isdigit(ch))  f=ch=='-'?-1:f,ch=getchar();
14     while(isdigit(ch))   x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
15     return x*f;
16 }
17 int a[N],n,m;
18 vector<int>    num;
19 struct node
20 {
21     int l,r,s;//左右儿子下标,区间中一共有多少个数
22 }tr[N*20];
23 int root[N],idx;//root[i]为第i课线段树的节点编号
24 #define id(x)    lower_bound(num.begin(),num.end(),x)-num.begin()
25 //id(x)为映射
26 int build(int l,int r)
27 {
28     int p=++idx;
29     if(l==r)    return p;
30     int mid=l+r>>1;
31     tr[p].l=build(l,mid),tr[p].r=build(mid+1,r);
32     return p;//返回当前节点的编号
33 }
34 inl int insert(int p,int l,int r,int x)
35 {
36     int q=++idx;//创建新点q
37     tr[q]=tr[p];//复制旧点p 
38     if(l==r)
39     {
40         tr[q].s++;
41         return q;
42     }
43     int mid=l+r>>1;
44     if(x<=mid)    tr[q].l=insert(tr[p].l,l,mid,x);//x出现在左子树,修改左子树
45     else    tr[q].r=insert(tr[p].r,mid+1,r,x);//x出现在右子树,修改右子树
46     tr[q].s=tr[tr[q].l].s+tr[tr[q].r].s;//统计
47     return q;//返回当前分配使用的新节点编号
48 }
49 inl int query(int q,int p,int l,int r,int k)//查询区间
50 {
51     if(l==r)    return l;
52     int s=tr[tr[q].l].s-tr[tr[p].l].s;//线段树相减
53     int mid=l+r>>1;
54     if(k<=s)    return query(tr[q].l,tr[p].l,l,mid,k);//左儿子数字大于或等于k时,说明第k小数在右子树
55     return query(tr[q].r,tr[p].r,mid+1,r,k-s);//否则在左子树
56 }
57 void init()
58 {
59     //离散化
60     sort(num.begin(),num.end());
61     num.erase(unique(num.begin(),num.end()),num.end());
62     root[0]=build(0,num.size()-1);
63     
64 }
65 int main(void)
66 {
67     n=read(),m=read();
68     for(regi i=1;i<=n;i++)
69     {
70         a[i]=read();
71         num.push_back(a[i]);
72     }
73     init();
74     for(regi i=1;i<=n;i++)    root[i]=insert(root[i-1],0,num.size()-1,id(a[i]));
75     while(m--)
76     {
77         int l=read(),r=read(),k=read();
78         printf("%d\n",num[query(root[r],root[l-1],0,num.size()-1,k)]);
79     }
80     return 0;
81 }

可持续化的思想都基本是这样的,包括可持续化平衡树,可持续化堆(注意是思想,比如可持续化堆有的时候就不能动态开点)

 

到了这里,关于主席树的简要讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态规划——区间dp [石子合并]

    动态规划(dp)是一种通过将问题分解为子问题,并利用已解决的子问题的解来求解原问题的方法。适用于具有重叠子问题和最优子结构性质的优化问题。通过定义状态和状态转移方程,动态规划可以在避免重复计算的同时找到问题的最优解,是一种高效的求解方法,常用于

    2024年02月12日
    浏览(45)
  • 动态规划系列 | 一文搞定区间DP

    区间 DP 可以用于解决一些涉及到区间合并或分割的问题。区间 DP 通常有以下三个特点: 合并(分割) :将两个或多个部分进行整合,或者反过来将一个区间分解成多个部分。 特征 :能将问题分解为能两两合并的形式。 求解 :对整个问题设最优解,枚举合并点,将问题分

    2024年02月02日
    浏览(45)
  • 石子合并(动态规划 区间DP)+详细注释

    原题链接   活动 - AcWing 题目 设有 N 堆石子排成一排,其编号为 1,2,3,…,N。 每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。 每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相

    2024年02月16日
    浏览(38)
  • PTA 7-18 求矩阵中的最大小值

    输入一个n×m的整数矩阵(n=10,m=10),然后输出其中的最大值、最小值,并输出这两个值的下标。 输入格式: 输入矩阵的行数n和列数m(n=10,m=10),然后输入所有矩阵中的数据。 输出格式: 第一行输出n×m的数组中的最大值及其下标。每两项之间一个空格。 第二行输出n×m的数组中的最

    2024年02月04日
    浏览(37)
  • 【动态规划】LeetCode 312. 戳气球 --区间DP问题

      Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接       我会一直往里填充内容哒! 🌈LeetCode专栏:专栏链接       目前在刷初级算法的LeetBook 。若每日一题当中有力所能

    2023年04月16日
    浏览(39)
  • 【TOTP】TOTP算法(基于时间的一次性动态密码)原理介绍 & 简要逻辑实现说明

    Time-base One-Time Password 翻译过来是 基于时间的一次性密码 。这里以QQ令牌为例,解释下TOTP。 首先,当用户首次使用QQ令牌时,服务器会向用户的手机APP上颁发一个证书/秘钥(这里理解为一个长的字符串,设为变量: secret ,颁发时间[unix时间戳]记为: createTimestamp ),单个临时

    2024年02月10日
    浏览(47)
  • 树的引进以及二叉树的基础讲解——【数据结构】

                                     W...Y的主页 😊 代码仓库分享  💕 当我们学习完前面的数据结构,难度也就会上升,但是这个也是非常重要的数据结构。今天我们来学习一种新的数据类型——树。 目录 树的概念以及结构 树的概念 树的相关概念 树的表示

    2024年02月07日
    浏览(40)
  • acwing算法基础之动态规划--线性DP和区间DP

    线性DP:状态转移表达式存在明显的线性关系。 区间DP:与顺序有关,状态与区间有关。 题目1 :数字三角形。 解题思路:直接DP即可, f[i][j] 可以来自 f[i-1][j] + a[i][j] 和 f[i-1][j-1] + a[i][j] ,注意 f[i-1][j] 不存在的情况(最后一个点)和 f[i-1][j-1] 不存在的情况(第一个点)。

    2024年02月04日
    浏览(49)
  • 蓝桥杯备赛之动态规划篇——涂色问题(区间DP)

    2023第十四届蓝桥杯模拟赛第二期个人题解(Java实现) 2023第十四届蓝桥杯模拟赛第三期个人题解(Java实现) 蓝桥杯备赛之动态规划篇——背包问题 蓝桥杯真题——单词分析(Java实现) 😘😘 哈喽,大家好!这里是蓝桥杯系列文章的动态规划章节🔥🔥,今天要讲解的是区

    2024年01月23日
    浏览(48)
  • 树的一些经典 Oj题 讲解

    先序遍历 我们知道 树的遍历有 前序遍历 中序遍历 后序遍历 然后我们如果用递归的方式去解决,对我们来说应该是轻而易举的吧!那我们今天要讲用迭代(非递归)实现 树的相关遍历 首先呢 我们得知道 迭代解法 本质上也是在模拟递归,因为递归的过程中使用了系统栈,

    2024年01月21日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包