poj 2104 K-th Number

题目描述
罗老师给大家n个不同的数字,a1,a2, … , an, 问你从小到大第k个的数字这个问题很简单。但现在罗老师要加大难度,要大家一口气回答m个询问,每个询问给定一个区间[x, y], 问你[x, y]之间从小到大排序后第k个数是多少?

输入
输入n, m
然后一行输入n个不同的整数
然后输入m行
每行输入x, y, k,表示询问[x, y]区间里从小到大排序后第k个数是多少

输出
对于每个询问输出第k个数字

样例输入
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
样例输出
5
6
3
提示
【样例说明】
[2, 5]区间是5 2 6 3, 排序后是2 3 5 6,第3个数是5
【数据规模和约定】
1<=n<=100000
1<=m<=5000
0<=ai<=100000000
1<=x<=y<=n
1<=z<=y-x+1

主席树模板题,总算搞懂了主席树
思想:
1.对每个[1,i]建一个线段树,线段树的区间表示l-r这些数有几个(数字要进行离散化)。
2.那么按照普通想法,空间需要 4n2 ,怎么办呢?
3.我们发现,每次i加1,只是一个数从根结点到叶结点的一条链发生了变化,也就是log(n)个点发生了变化,所以我们可以考虑用指针把那些与当前数无关的区间直接链起来即可。
4.用root[i]表示[1,i]这个线段树的根结点的编号
然后询问的时候,数的数量就可以用前缀和的方式计算。接下来,就是找第k个数的经典问题了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int n,m,x,y,z,tot;
int a[100005],c[100005];//原数,离散化后的数 
int root[100005];//i位置线段树的根 
int pos[100005];
struct node
{
    int v,id;
}b[100005];
struct ty
{
    int l,r,s; //左指针,右指针,数的数量
}tree[2200005];
int insert(int id,int l,int r,int x) //返回当前区间的编号,方便它的父亲把它链起来
{
    tot++;
    tree[tot]=tree[id]; //传递旧树信息,以后递归直接就访问旧树
    tree[tot].s++;
    id=tot;
    if(l==r) return tot;
    int mid=(l+r)/2;
    if(x<=mid) tree[id].l=insert(tree[id].l,l,mid,x); else tree[id].r=insert(tree[id].r,mid+1,r,x);
    return id;
}
int query(int l,int r,int x,int y,int k)
{
    if(l==r) return l;
    int mid=(l+r)/2;
    int sum=tree[tree[y].l].s-tree[tree[x].l].s;
    if(sum>=k) return query(l,mid,tree[x].l,tree[y].l,k);
    else return query(mid+1,r,tree[x].r,tree[y].r,k-sum);
}
bool cmp(node x,node y)
{
    return x.v<y.v;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]);
        b[i].v=a[i];
        b[i].id=i;
    }
    sort(b+1,b+n+1,cmp);
    int num=0;
    for(int i=1;i<=n;i++) //离散化
    {
        if(b[i].v!=b[i-1].v)  num++; 
        c[b[i].id]=num;
        pos[num]=a[b[i].id];
    }
    for(int i=1;i<=n;i++) root[i]=insert(root[i-1],1,num,c[i]);
    for(int i=1;i<=m;i++) 
    {
        scanf("%d%d%d",&x,&y,&z);
        int t=query(1,num,root[x-1],root[y],z);
        printf("%d\n",pos[t]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值