caioj1441:第k小的数Ⅰ

【传送门:caioj1441


简要题意:

  给出一个n个数的序列,m个询问,每个询问输入l,r,k,输出第l个数到第r个数第k小的数


题解:

  首先想到线段树,但是做不到询问区间的第几小,只能做到最大或最小或和

  所以我们用权值线段树——主席树来解决这道题


参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
struct node
{
    int lc,rc,c;//c维护的是这个区间维护了多少个节点
}tr[2100000];int len;
int n,m;
int a[110000],s[110000],root[110000];
int LS(int d)
{
    int l=1,r=n,ans=0;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(s[mid]<=d)
        {
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}
void Link(int &u,int l,int r,int p)
{
    if(u==0) u=++len;
    tr[u].c++;
    if(l==r) return ;
    int mid=(l+r)/2;
    if(p<=mid) Link(tr[u].lc,l,mid,p);
    else Link(tr[u].rc,mid+1,r,p);
}
void Merge(int &u1,int u2)
{
    if(u1==0){u1=u2;return ;}
    if(u2==0) return ;
    tr[u1].c+=tr[u2].c;
    Merge(tr[u1].lc,tr[u2].lc);
    Merge(tr[u1].rc,tr[u2].rc);
}
int Calc(int u1,int u2,int l,int r,int k)
{
    if(l==r) return s[l];
    int c=tr[tr[u1].lc].c-tr[tr[u2].lc].c;//用u1这棵线段树减u2这棵线段树就可以得到u1到u2这段区间的信息
    int mid=(l+r)/2;
    if(k<=c) return Calc(tr[u1].lc,tr[u2].lc,l,mid,k);
    else return Calc(tr[u1].rc,tr[u2].rc,mid+1,r,k-c);
}
int main()
{
    len=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        s[i]=a[i];
    }
    sort(s+1,s+n+1);
    memset(root,0,sizeof(root));
    for(int i=1;i<=n;i++)
    {
        Link(root[i],1,n,LS(a[i]));//每次插入以rt[i]为根一条链这条链维护i这个点的信息
        Merge(root[i],root[i-1]);//将其与前i-1条链合并,这样rt[i]为根的这条链维护的就是1~i的信息
    }
    for(int i=1;i<=m;i++)
    {
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",Calc(root[y],root[x-1],1,n,k));
    }
    return 0;
}
posted @ 2017-10-22 14:21  Star_Feel  阅读(234)  评论(0编辑  收藏  举报