洛谷 P3834 可持久化线段树 1(主席树)

传送门


主席树模板,求静态区间第k小。
据说主席树又叫可持久化线段树,因此它肯定跟线段树很有关系。

我们用n棵线段树来维护,第i棵线段树维护的是前1~i个元素的值。而每棵线段树上同一位置的节点维护的范围是一样的,若第i个元素的值是x,我们就在线段树里把x这个位置与管理它的节点的值都加1。
因为每一棵线段树的结构都是一样的,所以我们可以合并它们。

我们每次只需根据新输入的第i个值建一条链,然后与第i-1棵线段树合并(在当前链上累加,其他直接取等)。

而我们在求l ~ r范围内的第k小时,只需把第r棵线段树-第l-1棵线段树,就可以得到l ~ r范围的信息了。而我们在寻找时只需不停地找当前节点范围内的左儿子的值(小于mid的元素数量)c,若k<=c,去左儿子里找,反之去右儿子里找。


Code:

#include
#include
#define mid (l+r)/2
#define INF 1e9+1

int d[210000],lc[20100000],rc[20100000],tot[20100000],rt[20100000];
int n,m,v,len(0);

void update(int x,int &now,int l,int r)
{
    if(!now) now=++len;
    tot[now]=tot[x]+1;
    if(l==r) return;
    if(v<=mid)
    {
        rc[now]=rc[x];
        update(lc[x],lc[now],l,mid);
    }
    else
    {
        lc[now]=lc[x];
        update(rc[x],rc[now],mid+1,r);
    }
}

int find_kth(int x,int y,int k)
{
    int xx=rt[x-1],yy=rt[y];
    int l=-INF,r=INF;
    while(l<r)
    {
        int c=tot[lc[yy]]-tot[lc[xx]];
        if(k<=c)
        {
            xx=lc[xx];yy=lc[yy];
            r=mid;
        }
        else
        {
            xx=rc[xx];yy=rc[yy];
            l=mid+1;k-=c;
        }
    }
    return l;
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&d[i]);
    for(int i=1;i<=n;i++)
    {
        v=d[i];
        update(rt[i-1],rt[i],-INF,INF);
    }
    for(int i=1;i<=m;i++)
    {
        int l,r,k;
        scanf("%d %d %d",&l,&r,&k);
        printf("%d\n",find_kth(l,r,k));
    }
}

你可能感兴趣的:(主席树)