对主席树的理解以及使用

引入

一个长度为\(n\)的数组,有\(m\)次查询,每次查询区间\([l,r]\)内第\(k\)小的元素。

如果使用暴力,肯定不可以

使用线段树?可是我只会查询区间最值啊。

那么我们把问题再次简化一下,查询\([1,n]\)第\(k\)小的元素,要求使用线段树来实现。

权值线段树

为了解决这个问题,我们引入一个名词:权值线段树。那么权值线段树是如何解决上面那个问题的呢?

首先,我们对数组进行离散化处理,离散成为\([1,n]\),然后我们建一颗线段树,线段树的节点存放的即为对应区间的数的个数。

比如数组\(a={3,3,2,2}\),经过离散化后变为\(2,2,1,1\)。

对应的线段树即为:

建好线段树之后我们如何求解第\(k\)小元素呢?我们从根节点出发,看下它的左儿子的元素个数是否超过了\(k\),如果超过了\(k\),那么第\(k\)小一定是左儿子的第\(k\)小,我们直接去访问左儿子,否则,假设左儿子的节点为\(num\),那么第\(k\)小一定是右儿子的第\(k-num\)小,我们去访问右儿子,直到递归终止,我们便找到了第\(k\)小元素。

主席树

当我们解决了上一个问题,我们这样考虑:

每输入一个数字\(a_i\),就建一棵\([1,i]\)的权值线段树,那么如果要查询\([l,r]\)的区间第\(k\)小,直接让这两棵权值线段树做差,然后进行我们上面设计的算法,问题不久迎刃而解了吗?

但是,每建一棵树,这样\(n\)棵树的空间会达到\(O(n^2)\)的级别,空间是无法承受的。我们这样想,假设你输入了\(a_i\),并且你已经建好了\(a_{i-1}\)的线段树,是不是\(a_i\)和\(a_{i-1}\)的线段树只会有\(log\)级别的点是不同的,剩下的大部分都是完全一致的。利用这个性质,我们不再开辟新的线段树,而是先把\(a_i\)会改变掉的节点复制一份,然后对复制的节点进行修改,连接到上次构建好的线段树上,这样我们只用了\(log\)的空间。最终我们构造的这棵树就叫主席树(其实已经不是一棵树了)。点的个数最多为\(O(nlog(n))\)。

建立过程

对于数组\(a:3,3,2,2\)建立主席树:

第一步:离散化为\(2,2,1,1\)
第二步:输入\(2\),构造权值线段树

第三步:输入2

第四步:输入1

第五步:输入1

这样我们就构造了一个主席树(有点丑),然后对于要查询的区间\([l,r]\),我们只需要从他们各自的"根"出发,递归做差寻找第\(k\)大即可。图中四个根分别为\(1,4,7,10\)。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const ll N = 5e5+100;
vector<ll> v;
ll a[N],roots[N],cnt;
struct node{
    ll l,r,num;
}T[N*25];
void update(ll l,ll r,ll &x,ll y,ll pos){
    T[++cnt]=T[y];T[cnt].num++;x=cnt;//复制节点并且更新
    if(l==r) return ;
    ll mid=(l+r)>>1;
    if(mid>=pos) update(l,mid,T[x].l,T[y].l,pos);
    else update(mid+1,r,T[x].r,T[y].r,pos);
}
ll query(ll l,ll r,ll x,ll y,ll k){
    if(l==r) return l;
    ll sum=T[T[y].l].num-T[T[x].l].num;
    ll mid=(l+r)>>1;
    if(sum>=k) return query(l,mid,T[x].l,T[y].l,k);//第$k$小在左子树
    else return query(mid+1,r,T[x].r,T[y].r,k-sum);//在右子树
}
ll getid(ll x){
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int main(){
    cnt=0;
    ll n,m;
    scanf("%lld%lld",&n,&m);

    for(ll i=1;i<=n;i++) scanf("%lld",&a[i]),v.push_back(a[i]);
    sort(v.begin(),v.end());

    v.erase(unique(v.begin(),v.end()),v.end());//离散化

    for(ll i=1;i<=n;i++) update(1,n,roots[i],roots[i-1],getid(a[i]));
    while(m--){
        ll l,r,k;
        scanf("%lld %lld %lld",&l,&r,&k);

        printf("%lld\n",v[query(1,n,roots[l-1],roots[r],k)-1]);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/codancer/p/12232401.html

时间: 2024-10-09 18:22:28

对主席树的理解以及使用的相关文章

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

poj 2104 静态主席树

我的第一道主席树(静态). 先记下自己对主席树的理解: 主席树的作用是用于查询区间第k大的元素(初始化nlog(n),查询log(n)) 主席树=可持续线段树+前缀和思想 主席树实际上是n棵线段树(由于是可持续化线段树,所以实际上是n个长度为log(n)的链),第i棵线段树保存的是a[1]~a[i]这i个数的值域线段树,每个节点维护的是该值域中元素个数,这个是可以相减的,所以建完树后,查询[lf,rg]的第k大时,保存当前查询的值域区间在lf-1和rg这两棵线段树中的节点u,v(不理解看代码),

hdu 4348 To the moon (主席树)

hdu 4348 题意: 一个长度为n的数组,4种操作 : (1)C l r d:区间[l,r]中的数都加1,同时当前的时间戳加1 . (2)Q l r:查询当前时间戳区间[l,r]中所有数的和 . (3)H l r t:查询时间戳t区间[l,r]的和 . (4)B t:将当前时间戳置为t . 所有操作均合法 . 解法: 很明显是一道主席树的题 . 对于每一次区间加法都新建节点建一棵线段树,加个懒惰标记就行了,查询的话直接线段树区间求和 . 不过感觉这一题就是为可持续化数据结构写的,特别是时间戳

zoj 2112 Dynamic Rankings 带修改区间第K大 动态主席树

pass 首先,个人觉得把这个数据结构理解成树状数组套主席树是十分不严谨的.主席树的本质是可持久化权值线段树与前缀和思想的结合.而动态主席树是可持久化权值线段树与树状数组思想的结合.并非树套树般的泾渭分明的叠加. 其次,大概讲下对动态主席树的理解.我们静态主席树中,第i个版本维护的是[1,i]的权值线段树,我们利用前缀和的思想,通过y的版本-x的版本得到[x,y]的权值线段树,从而剥离出一颗对应区间的权值线段树.我们考虑在这个情况下,如果需要修改第a[i]的值,那么从i,i+1,i+2.....

主席树复习

T1 [CQOI2015]任务查询系统 n个任务,每个有运行的时间段和优先级,询问某一时刻,优先级最小的个任务的优先级之和 初做:  2017.2.4   http://www.cnblogs.com/TheRoadToTheGold/p/6366165.html 好像是做了一晚上来 现在:2017.3.27   14:17——15:56 用了接近2个小时做了一道以前做过的题,还是弱啊~~~~(>_<)~~~~ difference: 主席树维护的东西不同,以前直接存储优先级之和,现在存储的是

poj_2104: K-th Number 【主席树】

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

poj2104(主席树讲解)

今天心血来潮,突然想到有主席树这个神奇的玩意儿...一直都只是听说也没敢看.(蒟蒻蛋蛋的忧伤...) 然后到网上翻大神的各种解释...看了半天... 一拍脑袋...哇其实主席树 真的难...[咳咳我只是来搞笑的] 看了很多种解释最后一头雾水啊...就是没法脑补出(嗯没错经常脑补数据结构长啥样)主席树的样子.... 最后终于找到了一个大大大大大大神犇的ppt,看到了主席树的真面目,才真的能弄懂主席树的结构... -------------------------------------------

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

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

【Tyvj2133 BZOJ1146】网络管理Network(树套树,DFS序,树状数组,主席树,树上差分)

题意:有一棵N个点的树,每个点有一个点权a[i],要求在线实现以下操作: 1:将X号点的点权修改为Y 2:查询X到Y的路径上第K大的点权 n,q<=80000 a[i]<=10^8 思路:此题明显地体现了我对主席树理解不深 树上路径K大可以直接用树剖+二分答案+树做 但DFS序+主席树也可以 对于点U,它能影响DFS序上的区间(st[u],ed[u]) 所以维护方法就是类似序列K大一样 s[st[u]]++ s[ed[u]+1]-- 对于路径(x,y),信息为s[x]+s[y]-s[lca(x