主席树浅谈

静态主席树

在谈什么是主席树之前,不得不提这个名字的来历……发明主席树的dalao首字母缩写为(HJT)正好是某位伟人的名字,因此得名主席树( ̄▽ ̄)/

咳咳,现在我们正式介绍什么是主席树。


主席树实际上是一种有点抽象的数据结构,它所维护的每一个节点都是一颗线段树,因此我们需要一个\(root\)数组用以记录每个根节点的编号。而它的线段树则是维护区间\([1,i]\),\([1,i+1]\),……的前缀,是一种可持久化数据结构,你可以查询它历史版本的信息,或者通过其可加,可减的性质完成对某区间的查找。

例如想要查询第[l,r]区间的信息,那么,我们只需对\(root[r]-root[l-1]\)求前缀和即可。

显然,直接这样暴力对每个节点建树会MLE成傻子,但是我们动动脑筋就会发现:每次我们更新的仅仅是我们需要修改的点到根这条链上的信息,因此我们只需要重构这一条链,与上一个线段树相同的节点共用一下就行了。

如图:

这是我们的root[0],是一颗空树,接着我们加入一号元素(节点上的编号是其所属的线段树)

就这样,我们就可以完成空间上的节省


模板题静态区间第K小数

code:

#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;

struct node {
    int l,r,sum;
    node() {
        sum=0;//记录维护区间中的多少个数
    }
} t[100005*40];

int n,m,tot;
int root[100005],a[100005];
vector<int>v;

int getid(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}

void modify(int l,int r,int &x,int y,int pos)
{
    t[x=++tot]=t[y],t[x].sum++;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) modify(l,mid,t[x].l,t[y].l,pos);//小于mid找左子树
    else modify(mid+1,r,t[x].r,t[y].r,pos);//大于mid找右子树
}

int query(int l,int r,int x,int y,int k)
{
    if(l==r) return l;
    int mid=l+r>>1;
    int del=t[t[y].l].sum-t[t[x].l].sum;
    if(k<=del) return query(l,mid,t[x].l,t[y].l,k);
    else return query(mid+1,r,t[x].r,t[y].r,k-del);//删除左子树贡献
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++) scanf("%d",&a[i]),v.push_back(a[i]);
    sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());//离散化
    for(int i=1; i<=n; i++) modify(1,n,root[i],root[i-1],getid(a[i]));
    while(m--) {
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);//还原离散化的值
    }
}

动态主席树

待填坑

猜你喜欢

转载自www.cnblogs.com/KatouKatou/p/9570963.html