主席树(区间第k小)

//主席树 权值线段树+可持久化 
//权值线段树:在此处指各个数字在某个区间内出现的次数 
//那么第一棵权值线段树会记录[1,1]的数字出现次数 
//第n棵权值线段树会记录[1,n]的数字出现次数 
//例:数列为110001
//第一棵权值线段树记录为tree1[0]=0 tree1[1]=1
//第二棵权值线段树记录为tree2[0]=0 tree2[1]=2
//第六棵权值线段树记录为tree6[0]=3 tree6[1]=3
//那么要求区间为[3,5]的数字出现次数可拿第五棵权值线段树减去第(三减一)棵权值线段树 
//此处运用前缀和思想 
//将多棵权值线段树的公共点合为一个点可减少空间复杂度 
//求区间第k小即可求出区间数字出现次数后递归操作 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,cnt,b[200001],root[200001];//b[]离散化后的值 root[]根的编号 
struct uio{
    int num,id;
}a[200001];//离散化辅助结构体 
struct rty{
    int ls,rs,siz;//左儿子,右儿子,区间元素个数 
}tree[5000001];//权值线段树 递增使得左儿子表示的值小于右儿子表示的值 
bool cmp(uio x,uio y)
{
    return x.num<y.num;
}
void update(int l,int r,int k,int &now)//新建节点的左右儿子,新建节点值,当前节点 
{
    tree[++cnt]=tree[now];
    now=cnt;
    tree[now].siz++;
    if(l==r)
        return;
    int mid=(l+r)/2;
    if(k<=mid)//新建节点在左子树 
        update(l,mid,k,tree[now].ls);
    else update(mid+1,r,k,tree[now].rs);//新建节点在右子树 
}
int query(int l,int r,int x,int y,int k)//左右儿子,两棵权值线段树编号,第k大  
{
    if(l==r) 
        return l;
    int dif=tree[tree[y].ls].siz-tree[tree[x].ls].siz;//见本代码第9行 
    int mid=(l+r)/2;
    if(k<=dif)//左儿子的出现次数已大于等于k 说明k在左子树 
        return query(l,mid,tree[x].ls,tree[y].ls,k);
    else return query(mid+1,r,tree[x].rs,tree[y].rs,k-dif);//k在右子树
    //k不在左子树 但是左子树代表的数的出现次数包含在k中 因此要减去(同平衡树) 
}
void do_something()
{
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        printf("%d\n",a[query(1,n,root[u-1],root[v],w)].num);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].num);
        a[i].id=i;
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)
        b[a[i].id]=i;//离散化 
    for(int i=1;i<=n;i++)
    {
        root[i]=root[i-1];//暂时将新的根节点赋为原根节点的编号 
        update(1,n,b[i],root[i]);//新建一棵权值线段树 
    }
    do_something();
     return 0;
}

 

posted @ 2018-07-08 18:44  radishえらい  阅读(272)  评论(0编辑  收藏  举报