浅谈主席树

浅谈主席树思想以及模板


首先,作为一名蒟蒻,很早就听说过主席树这个高大上的名字,然而一直都不会(其实只是没敲过代码)。所以,今天就来正式的谈一谈主席树,本蒻著此文以记之

主席树的原理

它的原理十分简单,就是想办法把前后两棵线段树进行持久化并尽可能合并以节约空间。最后的空间复杂度大概是 O(nlogn)
这里写图片描述

然而这个时候它就会很自然地要求我们保证所有线段树的结构均完全相同,这时我们可以通过建立一个参考序列,里面保存着区间,映射到主席树里就是保存和这一区间有关的信息,然后对这个参考区间进行二分,这样映射到每棵线段树中它们的结构都将会是完全相同的

主席树的思想

就我个人的理解,主席树的本质,其实就是一颗可持久化线段树。但是本来一颗好好的线段树,我们为什么要把他们造成可持久化的呢,这是因为我们想访问多个区间或多个子线段树。此处的意思是我们建立一颗普通的线段树时,我们只是使这棵线段树对应了一个区间[ 1...n ],但往往我们并不想仅仅去访问这一棵线段树上的信息或者说这一段区间上的信(这可能是由于一棵线段树上的信息量太少而不能满足我们的需要)。有时我们可以直接使用原有的那棵线段树上的信息(比如我们想查询一段子区间的Maximum/Minimum或者是Sum什么的),然而有的时候就并不可以(如我们想求一段子区间中的第K大值)。对于什么时候Single-Segment-Tree会出现问题应该根据具体情况就可以判断出来

主席树的应用

作为一名蒟蒻,我做的题非常少,对于这种神奇的数据结构,应用可能有求某段区间中第K大值或者是求某段区间中某个或某段数字的出现次数等等,此处留坑待补

主席树的模板

下面的这个模板的功能是求某段区间第K小的值(当然是用主席树实现的),代码巨丑,不喜勿喷

#include<cstdlib>
#include<cstdio>
#include<algorithm>
#define maxn 100005
#define Pii pair<int,int>
#define Author Goseqh
using namespace std;
typedef struct node{
    int sum;
    node* left;
    node* right;
    node(int sum){
        this->sum=sum;
        right=left=NULL;
    }
    node():sum(0),left(NULL),right(NULL){}
}node;//主席树的结点
node* list[maxn];
node* op;
node* op2;
Pii line[maxn];
Pii sline[maxn];//排序之后的line数组(参考区间)
int n,m,a,b,c;
void insert(int l,int r,int o,int i){
    if(l==r)
        return;
    int m=(r-l)/2+l;
    if(line[i]>sline[m]){
        op->left=op2->left;
        node* k=new node(op2->right->sum+1);
        op->right=k,op=k,op2=op2->right;
        insert(m+1,r,o*2+1,i);
    }
    else{
        op->right=op2->right;
        node* k=new node(op2->left->sum+1);
        op->left=k,op=k,op2=op2->left;
        insert(l,m,o*2,i);
    }
    return;
}//主席树的二分插值过程
/*void init2(int l,int r,int x,node* now){
    if(l==r)
        return;
    int m=(r-l)/2+l;
    if(x>sline[m]){
        node* now->left=new node(0);
        node* now->right=new node(1);
    }
    else{
        node* now->left=new node(1);
        node* now->right=new node(0);
    }
    init2(l,m,x,now->left);
    init2(m+1,r,x,now->right);
    return;
}*///此处对应了另一种实现,即以第一棵有意义的线段树作为开头
void init2(int l,int r,node* now){
    if(l==r)return;
    int m=(r-l)/2+l;
    now->left=new node();
    now->right=new node();
    init2(l,m,now->left);
    init2(m+1,r,now->right);
    return;
}//这里是用一棵虚拟的线段树作为开头,感觉更加简洁不易出错
void init(){
    //init2(1,n,line[1],list[1]);
    list[0]=new node();
    init2(1,n,list[0]);
    for(int i=1;i<=n;i++){
        op=list[i]=new node(),op2=list[i-1];
        insert(1,n,1,i);
    }
    return;
}
int query(int l,int r){
    if(l==r)
        return sline[l].first;
    int con=op2->left->sum-op->left->sum;
    int m=(r-l)/2+l;
    if(c>con){
        c-=con;
        op=op->right,op2=op2->right;
        return query(m+1,r);
    }
    else{
        op=op->left,op2=op2->left;
        return query(l,m);
    }
}//询问过程
int main(){
    /*freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);*/
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&line[i].first);
        line[i].second=i;
        sline[i]=line[i];
    }
    sort(sline+1,sline+n+1);
    init();
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&c);
        op=list[a-1],op2=list[b];
        printf("%d\n",query(1,n));
    }
    return 0;
}

主席树的题目

这就等着其他的博文来进行补充啦O(∩_∩)O~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值