主席树(模板)

求区间第K大的值;

我们需要在短时间内回答数目巨大的问题,这个算法的核心是空间换时间;

每个点建一个线段树,是的;

我们先离散化所有权值,使得当前的权值在1到n范围内,恰巧是vector里的下标;

对于每一个点,我们分成左二子和右儿子,分别存放当前区间的左半部分和右半部分,维护左右节点的数量;

我们怎么求区间第K大呢?

运用前缀和的思想,我们其实建出来的很多树左右数量是递增的;

遍历每一个节点,现将当前节点继承前一节点的历史状态,再将节点数+1,根据当前节点的大小判断插在左边还是右边,递归进行此操作;

对于区间【l,r】,我们将l-1的树拿出来,r的树拿出来,每个树的节点数就是1到 i 的节点数,先两个左子树的节点数相减,得到的数与当前k比较,

如果大于k就在左子树里找,如果小于k就在右子树里找k-sum_L个;也是递归实现;

实质就是每次加点,查询时套用前缀和,根据数量判断向下递归的方向,从而减少时间复杂度;

时刻注意每个点代表的历史状态,谁和谁对应;

离散化操作

    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());

先将数组排序,然后unique(),此时函数操作是将重复的数字都扔到后面,返回不重复序列的最后一个数的下一个数的下标;这时我们将后面删去即可;

取得离散后的数lower_bound即可;

根据学长的传承,空间开40倍;

section (区间)

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e5+10;
vector<int> v;
int root_sec[maxn];
struct node
{
    int l,r,sum_pos;
}t[maxn*40];

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

int n,m,a[maxn],cnt;

void update(int l,int r,int x,int &y,int pos)
{
    y=++cnt;t[y]=t[x];t[y].sum_pos++;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,t[x].l,t[y].l,pos);
    else update(mid+1,r,t[x].r,t[y].r,pos);
}

int query_id(int l,int r,int x,int y,int sum_k)
{
    if(l==r) return l;
    int sum_sec=t[t[y].l].sum_pos-t[t[x].l].sum_pos;
    int mid=(l+r)>>1;
    if(sum_sec>=sum_k) return query_id(l,mid,t[x].l,t[y].l,sum_k);
    else return query_id(mid+1,r,t[x].r,t[y].r,sum_k-sum_sec);
}

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++) update(1,n,root_sec[i-1],root_sec[i],get_id(a[i]));
    for(int i=1;i<=m;i++)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",v[query_id(1,n,root_sec[l-1],root_sec[r],k)-1]);
    }
    return 0;
}

至于为什么叫主席树,是因为发明他的人叫HJT;

猜你喜欢

转载自www.cnblogs.com/WHFF521/p/11623322.html