测试地址:K-th Number
题目大意:给定一个长度为N的数列,要求处理M个询问,每个询问三个参数i,j,k,表示询问区间[i,j]内第k小的数。
做法:可持久化线段树的单点修改,网上有很多这里就不讲了。这里再简述一下这一题如何转化成可持久化线段树。首先将数列里所有数离散化成1,2,...,N,可持久化线段树的每一个节点维护一个权值区间,再维护一个cnt表示这个区间内的数出现过了多少次,一开始所有节点的cnt都初始化成0,建立一棵初始(第0棵)线段树。然后我们逐个加入原数列中的元素A[i](当然是离散化后的),这就等价于对线段树进行单点修改并产生一个新的版本,具体来说就是复制所有包含A[i]的权值区间的节点,然后在新复制的节点上的cnt增加1。这时候我们就可以知道,第i棵线段树上表示[l,r]这个权值区间的节点中cnt的意义就是:在原数列的区间[1,i]中,权值在权值区间[l,r]中的元素个数。
加入所有元素之后,我们就可以用二分查找来询问区间[i,j]的第k小值。要进行二分查找,首先要算出在原数列的区间[i,j]中权值在某一个权值区间中的元素个数,由前面的cnt的意义可知,N棵线段树其实是一种前缀和,因此把第j棵线段树与第i-1棵线段树的对应节点cnt值相减,所得到的线段树就可以表示出在原数列的区间[i,j]中权值在某一个权值区间中的元素个数。这棵线段树不用存起来,可以在二分查找时用相对应的节点直接相减得到。知道这一点之后,就可以在这棵线段树上对权值区间[1,N]进行二分查找,如果权值区间[1,N/2]中元素的出现次数不超过k,说明第k小的值在区间[1,N/2]中,从而继续向左子节点查找,否则说明第k小的值在区间(N/2,N]中,向右子节点查找。最后注意输出的值是离散化前的值即可。
可持久化线段树内存是硬伤......一定要开足数组!
上面讲了这么多,实际操作起来不难,代码连100行都不到,以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,a[100010],tot=0,rt[100010];
struct segnode
{
int lc,rc;
int cnt;
}seg[4000010];
struct forsort
{
int val,pos;
}fs[100010];
bool cmp(forsort a,forsort b)
{
return a.val<b.val;
}
void buildtree(int no,int l,int r)
{
seg[no].cnt=0;
tot=max(tot,no);
if (l==r)
{
seg[no].lc=seg[no].rc=0;
return;
}
seg[no].lc=no<<1,seg[no].rc=no<<1|1;
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
}
void insert(int &no,int last,int l,int r,int x)
{
no=++tot;
seg[no].cnt=seg[last].cnt;
seg[no].lc=seg[last].lc;
seg[no].rc=seg[last].rc;
if (l==r)
{
seg[no].cnt++;
return;
}
int mid=(l+r)>>1;
if (x<=mid) insert(seg[no].lc,seg[last].lc,l,mid,x);
else insert(seg[no].rc,seg[last].rc,mid+1,r,x);
seg[no].cnt=seg[seg[no].lc].cnt+seg[seg[no].rc].cnt;
}
int query(int fnt,int lst,int l,int r,int k)
{
if (l==r) return l;
int mid=(l+r)>>1,s=seg[seg[lst].lc].cnt-seg[seg[fnt].lc].cnt;
if (s>=k) return query(seg[fnt].lc,seg[lst].lc,l,mid,k);
else return query(seg[fnt].rc,seg[lst].rc,mid+1,r,k-s);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&fs[i].val);
fs[i].pos=i;
}
sort(fs+1,fs+n+1,cmp);
for(int i=1;i<=n;i++)
a[fs[i].pos]=i;
buildtree(1,1,n);
rt[0]=1;
for(int i=1;i<=n;i++)
insert(rt[i],rt[i-1],1,n,a[i]);
for(int i=1,s,t,k;i<=m;i++)
{
scanf("%d%d%d",&s,&t,&k);
printf("%d\n",fs[query(rt[s-1],rt[t],1,n,k)].val);
}
return 0;
}