[知识学习] 主席树

这两天学习了主席树,基本上搞懂了主席树是怎么操作的



主席树,是一种可持久化线段树。最简单的操作就是维护静态区间第 \(k\) 小

主席树通过维护历史版本,实现查询区间的有关操作


主席树的原理

假设现在有这么一个序列:\(4,1,3,5,2\)

问如何求出区间[1,3]内大小为第二的数?

利用大眼观察法,很显然是3

那么让计算机去怎么实现呢?它又没有眼睛

对于这个序列,我们可以先建一颗空的权值线段树,命名为“树0”(方便后面的使用),如图:

别告诉我你不知道什么是权值线段树,自己去百度;

现在序列里面第一个数是 \(4\),我们往树里面插入一个 \(4\) ,因为要保留历史版本,所以我们对 \(4\) 这个数新建一颗线段树,命名为“树1”,如图:

为什么是这样呢?

\(1\le 4 \le5\),故区间[1,5]++;

\(4\le 4 \le5\),故区间[4,5]++;

\(4\le 4 \le4\),故区间[4,4]++;

其他的还是 \(0\) ;

懂了没有。。。

继续插入第二个数 \(1\) ,建成“树2” ,这里不解释了

再插入第三个数 \(3\),建成“树3”

OK!现在我们就已经可以求出[1,3]内的大小为第二大的数了

递归操作查询排名应该都会吧?

不会的看这里:

  • 进入[1,5]节点,我们发现他的左儿子的子树个数为\(2\) , $2\le k $ \((k=2)\),于是进入[1,3]节点;
  • 然后我们发现[1,3]节点的左儿子子树个数\(1 < k\) \((k=2)\),于是进入[3,3]节点;
  • 此时我们把\(k\)更新为\(1\) (\(2-1=1\));
  • 走到头了,于是就返回3,所以答案就是3,也就是原来的序列区间[1, 3]的第2小就是3

现在你明白了主席树是怎么操作了的吧?


疑问

但是有一个问题:上面我们求的是区间[1,3]的第 \(k\) 大的数

同理,区间[1,r] (\(r \in [1,n],r \in N\))的第 \(k\) 大数我们也就会求了

那怎么求区间[l,r]的第 \(k\) 大数呢?

举个例子,求区间[2,3]的第 \(k\) 大数

我们拿建出来的“树3”减掉“树1”后,再进行如上操作就可以了

这也就是前缀和思想

所以对于区间[l,r] 我们拿“树r”减去“树(l-1)”,再query一下就可以求得答案了


例题与代码

那么主席树就介绍完了,具体实现给个例题让大家看看,还有不懂得可以再参考一下他人的博客

例题:静态区间第 \(k\) 小(【模板】可持久化线段树1/主席树)

题目链接

(鸣谢@wsk1202提供板子)

#include <bits/stdc++.h>
#define N (200000+5)
#define ls ch[rt][0]
#define rs ch[rt][1]
#define vl ch[vs][0]
#define vr ch[vs][1]//几个宏定义
using namespace std;
int n,m,q;
int rt[N],ch[N<<5][2],tot,val[N<<5];
int a[N],b[N];
inline int query(int x){//离散化
    return lower_bound(b+1,b+m+1,x)-b;
}
inline void update(int &rt,int vs,int l,int r,int k){//新建一棵树
    rt=++tot;
    ls=vl,rs=vr;
    val[rt]=val[vs]+1;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(k<=mid) update(ls,vl,l,mid,k);
    else update(rs,vr,mid+1,r,k);
}
inline int query(int rt,int vs,int l,int r,int k){//询问
    if(l==r) return l;
    int mid=(l+r)>>1;
    int v=val[vl]-val[ls];
    if(k<=v) return query(ls,vl,l,mid,k);
    else return query(rs,vr,mid+1,r,k-v);
}
int main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
    sort(b+1,b+n+1);
    m=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++){
        update(rt[i],rt[i-1],1,n,query(a[i]));
    }
    while(q--){
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",b[query(rt[l-1],rt[r],1,n,k)]);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Xx-queue/p/12208581.html

时间: 2024-10-13 05:32:50

[知识学习] 主席树的相关文章

「日常训练与知识学习」树的分块(王室联邦,HYSBZ-1086)

题意与分析 这题的题意就是树分块,更具体的看题目(中文题). 学习这一题是为了树的分块,为树上莫队做铺垫. 参考1:https://blog.csdn.net/LJH_KOQI/article/details/52326103 参考2:https://blog.csdn.net/popoqqq/article/details/42772237 注意到题目要求某块区域所有的点到根的路径上的点都属于该区域.因此不能够暴力地去dfs,每找到\(B\)个分一块是不可取的,因为无法保证联通性(一颗子树的下

poj2104(主席树)

K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 35704   Accepted: 11396 Case Time Limit: 2000MS Description You are working for Macrohard company in data structures department. After failing your previous task about key inse

zoj2112 主席树动态第k大 (主席树&amp;&amp;树状数组)

Dynamic Rankings Time Limit: 10 Seconds      Memory Limit: 32768 KB The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They

zoj 2112 Dynamic Rankings(树状数组套主席树)

题意:对于一段区间,每次求[l,r]的第k大,存在单点修改操作: 思路: 学习主席树参考: http://blog.csdn.net/wjf_wzzc/article/details/24560117(各种形式) http://blog.csdn.net/bossup/article/details/31921235(推荐) http://blog.csdn.net/xiaofengcanyuexj/article/details/25553521?utm_source=tuicool(图解)

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

题目背景 这是个非常经典的主席树入门题--静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个正整数,表示这个序列各项的数字. 接下来M行每行包含三个整数l, r, kl,r,k , 表示查询区间[l, r][l,r] 内的第k小值. 输出格式: 输出包含k行,每行1个正整数,依次表示每一次查询的

主席树的学习

前言 主席树可真是个好东西 之前一直都觉得挺难的 今天一看 woc这么简单! 怎么可能,我还是太蒟蒻了 感谢akakw1大佬的指导! 正文: 一.前置知识及算法思路 1.可持久化 因为主席树是可持久化线段树,所以还是有必要了解一下可持久化 可持久化的数据结构是可以支持访问任一历史版本的(也就是每一次修改操作之前的情况) 2.如何实现 以可持久化线段树为例: 很自然的,我们可以想到对于每一个版本开一个线段树 但考虑到这样做的空间复杂度是\(O(k*n*4)\) (k为修改次数) 果断放弃 3.优化

[学习笔记]主席树

权值线段树 线段树上每个区间记录的是区间内所有数出现次数的总和. 然后就可以求出整棵线段树的第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