题目描述
罗老师给大家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;
}