K-th Number 线段树(归并树)+二分查找

时间:2023-03-08 18:34:39

                            K-th Number

题意:给定一个包含n个不同数的数列a1, a2, ..., an 和m个三元组表示的查询。对于每个查询(i, j, k), 输出ai, ai+1, ... ,aj的升序排列中第k个数 。

题解:用线段树,每个节点维护一个区间并且保证内部升序,对于每次查询x,返回该区间小于x的数的个数。就这样不断二分,直到找到x为止。

线段树(归并树)+二分查找

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <sstream>
#include <iomanip>
using namespace std;
typedef long long LL;
const int INF=0x4fffffff;
const int EXP=1e-;
const int MS=; struct node
{
int l,r;
vector<int> vec;
}nodes[*MS]; int a[MS];
int num[MS];
int n,m; void build(int root,int l,int r)
{
nodes[root].l=l;
nodes[root].r=r;
nodes[root].vec.clear();
if(r-l==)
{
nodes[root].vec.push_back(a[l]);
return ;
}
int mid=(l+r-)>>;
build(root<<,l,mid+);
build(root<<|,mid+,r);
nodes[root].vec.resize(r-l);
merge(nodes[root<<].vec.begin(),nodes[root<<].vec.end(),
nodes[root<<|].vec.begin(),nodes[root<<|].vec.end(),nodes[root].vec.begin());
} int query(int root,int l,int r,int x)
{
if(r<=nodes[root].l||nodes[root].r<=l)
return ;
else if(nodes[root].l>=l&&nodes[root].r<=r)
return upper_bound(nodes[root].vec.begin(),nodes[root].vec.end(),x)-nodes[root].vec.begin();
else
{
int lcnt=query(root<<,l,r,x);
int rcnt=query(root<<|,l,r,x);
return lcnt+rcnt;
}
} int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=;i<n;i++)
{
scanf("%d",&a[i]);
num[i]=a[i];
}
sort(num,num+n);
build(,,n);
int s,t,k;
for(int i=;i<m;i++)
{
scanf("%d%d%d",&s,&t,&k);
s--;
int l=-,r=n-;
/* 注意 根据问题特点,这里应该是l=-1,r=n-1.
如果情况是询问[l,n]这个区间第n-l+1大的值,并且这个值在最后的位置,那么最后的结果会是num[n],越界
也就是说 二分的结果总是l=mid,知道r-l<=1; 细节问题需要注意
*/
while(r-l>)
{
int mid=(l+r)>>;
int cnt=query(,s,t,num[mid]);
if(cnt>=k)
r=mid;
else
l=mid;
}
printf("%d\n",num[r]);
}
}
return ;
}

我写的块状数组超时了。不知道是算法真的超时,还是细节问题导致超时。日后再来改正。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <sstream>
#include <iomanip>
using namespace std;
typedef long long LL;
const int INF=0x4fffffff;
const int EXP=1e-;
const int MS=;
const int SIZE=; int n,m;
int a[MS];
int order[MS]; vector<int> bucket[MS/SIZE]; int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=;i<MS/SIZE;i++)
bucket[i].clear(); // 千万注意清空
for(int i=;i<n;i++)
{
scanf("%d",&a[i]);
bucket[i/SIZE].push_back(a[i]);
order[i]=a[i];
}
sort(order,order+n);
for(int i=;i<n/SIZE;i++)
sort(bucket[i].begin(),bucket[i].end());
int s,t,k;
while(m--)
{
scanf("%d%d%d",&s,&t,&k);
s--; //[s,t) 二分查找使用左必有开更方便一些
int l=-,r=n-; // 注意: 根据问题的性质,l=0,r=n是错误的,因为有情况总是mid=l,一直到到
// n-l<=1, 这时答案是num[n],不在给定的数组范围内了。
while(r-l>)
{
int mid=(l+r)>>;
int x=order[mid];
int tl=s,tr=t,c=;
// 处理区间两端多出的部分
while(tl<tr&&tl%SIZE!=)
if(a[tl++]<=x)
c++;
while(tl<tr&&tr%SIZE!=) // 左闭右开 处理方便一些
if(a[--tr]<=x)
c++;
// 对每一个桶进行统计
while(tl<tr)
{
int id=tl/SIZE;
c+=upper_bound(bucket[id].begin(),bucket[id].end(),x)-bucket[id].begin();
tl+=SIZE;
}
if(c>=k)
r=mid;
else
l=mid;
}
printf("%d\n",order[r]);
}
}
return ;
}