主席树的学习

前言

主席树可真是个好东西

之前一直都觉得挺难的

今天一看

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\n",b[tmp]);
91     }
92     return 0;
93 }

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

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

话说会有人看吗QAQ

原文地址:https://www.cnblogs.com/Zenyz/p/9873574.html

时间: 2024-10-14 16:22:16

主席树的学习的相关文章

【算法】主席树

这是一篇有关主席树的总结 主席树是什么? 对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的 PS:本篇随笔对于主席树的基本内容并没有深刻讲解,主要说明它的一些用法 其实就是很多一堆大量的权值线段树 (什么是权值线段树?就是每个节点维护不是位置,而是权值,比如 \([1,4]\) 维护的就是权值等于1到权值等于4的信息) 而且这些线段树还隐含了一个前缀和的功能 最简单的就是一个数列,长度为 \(n\) ,那么就建 \(n\) 棵权值线段树,第 \(i\

[知识学习] 主席树

这两天学习了主席树,基本上搞懂了主席树是怎么操作的 主席树,是一种可持久化线段树.最简单的操作就是维护静态区间第 \(k\) 小 主席树通过维护历史版本,实现查询区间的有关操作 主席树的原理 假设现在有这么一个序列:\(4,1,3,5,2\) 问如何求出区间[1,3]内大小为第二的数? 利用大眼观察法,很显然是3 那么让计算机去怎么实现呢?它又没有眼睛 对于这个序列,我们可以先建一颗空的权值线段树,命名为"树0"(方便后面的使用),如图: 别告诉我你不知道什么是权值线段树,自己去百度:

[学习笔记]主席树

权值线段树 线段树上每个区间记录的是区间内所有数出现次数的总和. 然后就可以求出整棵线段树的第k大的数了(类似于二叉查找树?) 主席树 建立$n$棵上述的权值线段树,第$i$棵表示$a_1-a_i$的所有数组成的权值线段树. 用可持久化线段树的思想会发现,第$i$棵线段树与第$(i-1)$棵线段树之间只有$logn$个区间值是不同的,所以每次只要新建$logn$个区间,总复杂度是$O(nlogn)$. 区间查询类似于前缀和. 例题 bzoj4408

主席树(函数式线段树)学习小结(附手绘讲解图片)

主席树是一种离线数据结构,是由很多棵线段树组成的. 第i棵线段树存的是前i个数的信息: 每一个线段存数字的出现次数(因此建树之前要离散化). 那么n棵线段树不会MLE吗? 当然会了! 但是我们发现第i棵线段树和第i-1棵线段树是非常相似的,有许多结点完全相同,因此可以借用之前的结点,没必要新建结点. 具体建树方法建下图: 序列为 1 3 4 2 那么如果要询问i-j之间数字出现的次数怎么办呢? 因为每一棵线段树的区间都是相同的,所以要求l-r之间的数字的出现次数只要用前r位出现的次数减去前l-1

主席树学习

很好的博客:https://blog.csdn.net/qq_39809664/article/details/79934516 可持久化数组 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #include<queue> #define ll long l

BZOJ_3207_花神的嘲讽计划1_(Hash+主席树)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=3207 给出一个长度为\(n\)的串,以及\(m\)个长度为\(k\)的串,求每个长度为\(k\)的串在原串\([x,y]\)区间是否出现过. 分析 这道题要求对比长度为\(k\)的串,于是我们把这些串的Hash值都算出来,问题就转化成了求\([x,y]\)的区间中是否出现过某Hash值. 求区间中某一个值出现了多少次,可以用主席树. p.s. 1.学习了主席树指针的写法,比数组慢好多啊...

poj_2104: K-th Number 【主席树】

题目链接 学习了一下主席树,感觉具体算法思路不大好讲.. 大概是先建个空线段树,然后类似于递推,每一个都在前一个"历史版本"的基础上建立一个新的"历史版本",每个历史版本只需占用树高个空间(好神奇!) 查询时这道题是通过"历史版本"间作差解决 *另外提一下,在建立"历史版本"的过程中,是"新建",而不是"更新",是先copy过来原有的,再按个人需求改成自己的,所产生的一个新的"

主席树初学 SPOJ3267

别的没管,直接上的kuangbin代码,懂是基本懂了,然而主席树博大精深们还要多多学习. #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <stri

【BZOJ 1901】【Zju 2112】 Dynamic Rankings 动态K值 树状数组套主席树模板题

达神题解传送门:http://blog.csdn.net/dad3zz/article/details/50638360 说一下我对这个模板的理解: 看到这个方法很容易不知所措,因为动态K值需要套树状数组,而我一开始根本不知道该怎么套,, 学习吧,,, 然后我自己脑补如果不套会如何?后来想到是查询O(logn),修改是O(nlogn),很明显修改的复杂度太大了,为了降低修改的复杂度,我们只得套上树状数组来维护前缀和使它的n的复杂度降低为logn,从而修改的复杂度变为O(log2n).但因为我们套