首页 > 其他分享 >主席树

主席树

时间:2024-08-02 22:50:31浏览次数:7  
标签:cur int 线段 tree 编号 节点 主席

主席树(可持久化权值线段树)

这是一种用于处理一段数(e.g.1,2,3,4,5)在一个序列的某个区间[l,r]上出现几次/区间k小/查询历史版本的数据结构

前言:权值线段树

权值线段树可以维护某个区间内数组元素出现的次数。

1.主席树是什么?

对于一颗权值线段树,我们要往里面添加n个数字,我们知道,这很容易实现,只需要一个for循环,然后调用n次modify函数即可,但是如果需要一个记忆化的过程(可持久化),即我们每添加一个元素都需要记住这个权值线段树的状态,以便于我们对历史版本进行操作,那么如果只使用权值线段树,则我们需要n个权值线段树同时保存状态,这样做空间一定会爆炸,主席树则是解决这一问题的方法。

首先观察,每次更改(添加)一个数,线段树的变化
//图例
可以发现,对于每次添加,只会有一条以根节点为起点,代表被修改数字的叶子节点为终点的链有改动(长度为log n)因此,我们可以每次需改新建一条链,并将其与原树的某节点相连,形成新版本的线段树
时间复杂度O(NlogN)空间复杂度O(N+NlogN)

2.主席树实现

(1)建树先准备struct

与一般线段树不同,主席树每个节点不仅要记录权值还要记录左/右儿子的编号(因为每次要新建一条链)
用结构体存(数组要开logN倍以上)

(2)开始建树build

递归左右儿子,并记录编号

(3)要支持修改(添加一个数)modify

像线段树一样递归,每递归到一个节点,意味着它需要更新,复制一个新节点,向下递归,由于每个节点只有一个儿子与目标节点相交,所以新节点有一个儿子为原树上的节点,一个节点为新节点,向下递归并记录新儿子编号

(4)要支持查询 check

同线段树一样

3.典题精讲

静态区间第k小
建一个空树
对于每个\(a_i\)(小至大)进行一次修改操作形成n棵线段树
查询[l,r]区间第k小ans
已知ans属于[1,maxn]即根节点范围
令check(i,j)为已i为根(只含区间\(a_1~a_i\)的数)j号节点范围中数的数量
发明者的原话:“对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的”

可以加减的理由:主席树的每个节点保存的是一颗线段树,维护的区间信息,结构相同,因此具有可加减性

详细查询过程: 原始区间: [1,2,3] 查询 1到3 里的第二小的值(应该为2)

我们查询 l=1 r=3 k=2 因此这样查询:check(r,r) - query(l-1,l-1)(sum[r]-sum[l-1])
首先进入query函数,当前为12编号根节点,前一个为1编号根节点。对于编号12的根节点的左孩子编号为10,编号为1的根节点左孩子是2,让sum[10]-sum[2],sum记录的是此节点的val,相减得2,因为k=2,所以进入左子树递归。
当前为10编号根节点,前一个为2编号根节点。对于编号10的根节点的左孩子编号为8,编号为2的根节点左孩子是3,让d=(sum[8]-sum[3]),sum记录的是此节点的val,相减得1,因为k=2,所以进入右子树递递归,同时 k - d
当前为11编号根节点,前一个为4编号根节点。此时已经到达了叶子节点,返回 pl或者pr,得结果为 2(此时pl== pr==2,达到编号为4的叶子节点)

include<bits/stdc++.h>
using namespace std;
int a[200010],c[200010];  //c为离散化
int root[200010],top,n,m;
struct node{
    int ls,rs,val;
}tree[40*200010];
int build(int cur,int l,int r){
    cur=++top;
    tree[cur].val=0;
    if(l==r) return cur;
    int mid=(l+r)>>1;
    tree[cur].ls=build(cur,l,mid);
    tree[cur].rs=build(cur,mid+1,r);
//    cout<<cur<<' '<<l<<' '<<r<<" "<<tree[cur].val<<" "<<tree[cur].ls<<" "<<tree[cur].rs<<endl;
    return cur;
}
int clone(int pre){
    ++top;
    tree[top].ls=tree[pre].ls;
    tree[top].rs=tree[pre].rs;
    tree[top].val=tree[pre].val+1;
    return top;
}
int modify(int pre,int x,int l,int r){
    int cur=clone(pre);
    if(l==r) return cur;
    int mid=(l+r)>>1;
    if(x<=mid){
        tree[cur].ls=modify(tree[cur].ls,x,l,mid);
    }
    else{
        tree[cur].rs=modify(tree[cur].rs,x,mid+1,r);
    }
//    cout<<cur<<' '<<l<<' '<<r<<" "<<tree[cur].val<<" "<<tree[cur].ls<<" "<<tree[cur].rs<<endl;
    return cur;
}
int check(int cur,int pre,int l,int r,int x){
    int L1=tree[cur].ls;
    int L2=tree[pre].ls;
 //   cout<<cur<<' '<<pre<<" "<<L1<<" "<<L2<<endl;
    if(l==r) return l;
    int mid=(l+r)>>1;
    int num=tree[L1].val-tree[L2].val;
    if(num>=x){
        return check(tree[cur].ls,tree[pre].ls,l,mid,x);
    }
    else{
        return check(tree[cur].rs,tree[pre].rs,mid+1,r,x-num);  //x-num
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i],c[i]=a[i];
    sort(c+1,c+n+1);
    int cnt=unique(c+1,c+n+1)-c-1;
    root[0]=build(0,1,cnt);
//    cout<<endl;
//    cout<<cnt;
    for(int i=1;i<=n;i++){
        int num=lower_bound(c+1,c+cnt+1,a[i])-c;
        root[i]=modify(root[i-1],num,1,cnt);
    }
    while(m--){
        int l,r,k;
        cin>>l>>r>>k;
        int ans=check(root[r],root[l-1],1,cnt,k);
        cout<<c[ans]<<endl;
    }
    return 0;
}

标签:cur,int,线段,tree,编号,节点,主席
From: https://www.cnblogs.com/grylls2012/p/18339768

相关文章

  • 主席数学习笔记
    笛卡尔树太难了啊。主席树可持久化数据结构\((\text{Persistentdatastructure)}\)总是可以保留每一个历史版本,并且支持操作的不可变特性\(\text{(immutable)}\)。意思就是可以查询历史值,对于每一个历史版本\(k\),都是经过第\(k-1\)个版本、修改重连\(\logn\)个节点......
  • 主席树
    引入我们考虑对于一个长度为\(n\)的序列去找第\(k\)小,如果不用排序的话(虽然用了桶),可以利用一个桶匠所有数纪录下来,然后在桶上做二分即可(不会这个都不会吧),那么对于一个区间的话,我们便可以在区间\([l,r]\)上开桶然后做二分,不过这个桶我们该如何维护呢,首先我们想到前缀和,因为......
  • 学习笔记-主席树
    学习笔记-主席树主席树,就是可持久化权值线段树,也叫函数式线段树引入考虑如下问题:给定一个数列,查询其中第k大值显然,我们可以建一棵权值线段树,直接在上面二分就好了,即对于每个结点,查看它左子树的结点数量是否大于k,设为\(sum\)如果\(sum\gek\),则第k个结点在其左子树中,否则......
  • hdu4417(权值离散化后建立主席树)
    Problem-4417(hdu.edu.cn)马里奥是举世闻名的水管工。他“魁梧”的身材和惊人的跳跃能力让我们想起了。现在可怜的公主又遇到了麻烦,马里奥需要拯救他的爱人。我们将通往老板城堡的道路视为一条线(长度为n),在每个整数点i上都有一块高度为hi的砖。现在的问题是,如果马里奥可......
  • hdu4348(主席树区间修改)
    Problem-4348(hdu.edu.cn)BackgroundToTheMoon是一款独立游戏,于2011年11月发布,是一款由RPGMaker提供支持的角色扮演冒险游戏。《去月球》的前提是基于一种技术,该技术使我们能够永久地重建垂死之人的记忆。在这个问题上,我们将给你一个机会,实现幕后的逻辑。您已经获得了N......
  • 算法随笔——主席树(可持久化线段树)
    介绍主席树即可持久化线段树,由hjt大佬发明。好像又称函数式线段树。可以查询序列在任意时间的历史状态。学习链接学习链接主要思路维护历史状态,若采用直接拷贝整棵树的方式,空间爆炸。可以发现每次修改只有部分节点发生了改变(其实就是到树根那条链会改变),因此每次只需要记......
  • 主席树的简要讲解
    区间第k小值主席树是解决动态查找序列上[l,r]区间中的第k小值的一个数据结构核心思想:动态开点(后面会讲)传统线段树都是值域线段树其实意思就是每个节点都存的是序列上[l,r]的一个区间和,但是考虑我们需要动态处理区间的不是最值,故换一种线段树主席树一般用的是权值线段树就是把[......
  • 「主席树维护高精度」 CF464E The Classic Problem
    发现难点在于维护\(dis_u\)(起点\(s\)到\(u\)的最短路)。如果使用暴力高精度,复杂度会乘上一个\(len\)。考虑边权\(2^w\)的特殊性质,如果使用一个二进制串来维护\(dis\),那么加上边权\(2^w\)相当于将二进制下某一段\(1\)置为\(0\),然后再将一个\(0\)置为\(1\)。说白了......
  • 主席树的板子题
    题解方法1.可持久化线段树(主席树),代码有详细注释做法:先把值离散化在数值上建立线段树,维护每个数值区间中一共有多少个数问题1:如何求整体第K小数?答:二分,如果0~mid中有cnt数,cnt>=k,递归左边,如果cnt<k,递归右边,找k−cnt小的数。时间复杂的logn问题2:求【1,R】这个区间的第K......
  • 2024 ICPC Asia Pacific Championship-K-线段树合并or主席树
    比赛链接:https://codeforces.com/contest/1938给一棵有根树,执行以下代码:letLbeanemptyarrayforx=1ton fory=1ton append((x-1)*n*n+(LCA(x,y)-1)*n+(y-1))toLsortLinnon-decreasingorder然后进行\(q\)次询问,每次问\(L\)中第......