求区间第k大有很多种方法比如可持久化线段树,分桶法和平方分割 当然,我们还可以用归并树;(请原谅蒟蒻才晓得归并树,写的不好)。
我们把数列用线段树维护起来。线段树的每个节点都保存了对应区间排好序后的结果。在这之前我接触到的线段树上面保存的都是数值,而这次有所不同,每个节点保存的是数列。
如图(画的丑,勿喷):
建立线段树的过程和归并排序类似,每个节点的数列是其两个儿子的数列进行排序的结果。建树的复杂度是O(nlogn);其实这棵树也就是归并排序的完整体现。
那么要找区间第k大,我们通过二分答案来判断。就像分桶法和平方分割 一样,只是判定方式有区别。
要找区间内比当前x小的数的个数,我们可以用这棵树来查找。
- 如果所给的区间和当前节点的区间没有交集,返回0;
- 如果所给的区间完全包含了当前区间,那么用二分查找的方法来对当前节点上保存的数列进行查找(比如可以用upper_bound);
- 否则对两个儿子进行递归计算后求和返回;
由于对于线段树需要logn个区间,每个区间查找需要logn次,查找一次的时间复杂的为O((logn)^2);
所以总的时间复杂度是O(nlogn+m(logn)^3);
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define lch (rt<<1)
#define rch ((rt<<1)+1)
const int MAXN=100005;
int a[MAXN],nums[MAXN];
vector<int>data[MAXN*4];
int n,m;
void Build(int rt,int l,int r)
{
if(l==r)
{
data[rt].push_back(a[l]);
}
else
{
int mid=(l+r)>>1;
Build(lch,l,mid);
Build(rch,mid+1,r);
data[rt].resize(r-l+1);
merge(data[lch].begin(),data[lch].end(),data[rch].begin(),data[rch].end(),data[rt].begin());
}
//for(int i=0;i<int(data[rt].size());i++)
// printf("%d ",data[rt][i]);
//printf(":%d %d %d\n",rt,l,r);
}
int check(int i,int j,int x,int rt,int l,int r)
{
if(j<l||r<i)
return 0;
if(i<=l&&r<=j)
return upper_bound(data[rt].begin(),data[rt].end(),x)-data[rt].begin();
int mid=(l+r)>>1;
return check(i,j,x,lch,l,mid)+check(i,j,x,rch,mid+1,r);
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
nums[i]=a[i];
}
sort(nums+1,nums+n+1);
Build(1,1,n);
for(int i=1;i<=m;i++)
{
int l,r,k;
scanf("%d %d %d",&l,&r,&k);
int lb=0,ub=n+1;
while(ub-lb>1)
{
int mid=(lb+ub)>>1;
if(check(l,r,nums[mid],1,1,n)>=k)
ub=mid;
else
lb=mid;
}
printf("%d\n",nums[ub]);
}
}
注:merge函数的作用是:将两个有序的序列合并为一个有序的序列。函数参数:merge(first1,last1,first2,last2,result,compare);如果要合并的是vector要事先用resize。