链接:点击打开链接
题意:给定一个数列a1,a2,...,an和m个三元组表示的查询。对于每个查询(i,j,k),输出ai,...,aj的升序排列中的第k个数
代码1:
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int SIZE=100005;
int num[SIZE];
vector<int> tree[SIZE<<2];
void build(int l,int r,int rt){
int m;
if(l==r){
tree[rt].push_back(num[l]);
return ;
}
m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
tree[rt].resize(r-l+1);
merge(tree[rt<<1].begin(),tree[rt<<1].end(),tree[rt<<1|1].begin(),tree[rt<<1|1].end(),
tree[rt].begin());
} //每个节点由线段数的一个值,变为一个数列
int query(int L,int R,int x,int l,int r,int rt){
int m,ans;
if(L<=l&&r<=R)
return upper_bound(tree[rt].begin(),tree[rt].end(),x)-tree[rt].begin();
ans=0;
m=(l+r)>>1;
if(R<=m)
ans+=query(L,R,x,l,m,rt<<1);
else if(L>m)
ans+=query(L,R,x,m+1,r,rt<<1|1);
return ans;
} //其余同线段树基本是一样的
int main(){
int a,b,c,i,j,l,r,ans,mid,tmp;
while(scanf("%d%d",&n,&m)!=EOF){ //求第k个数,这题时间比较宽裕,因此可以用归并树加二分,但最好的方法依然是
for(i=1;i<=n;i++) //划分树
scanf("%d",&num[i]);
build(1,n,1);
sort(num+1,num+n+1);
while(m--){
scanf("%d%d%d",&a,&b,&c);
l=1,r=n;
while(l<=r){ //二分找第k个数
mid=(l+r)>>1;
tmp=query(a,b,num[mid],1,n,1);
if(tmp>=c){
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
printf("%d\n",num[ans]);
}
}
return 0;
}
代码2:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int SIZE=100005;
int tmp[SIZE],toleft[50][SIZE]; //toleft[i][j]为第i层前j个值有多少个进入左树
int tree[50][SIZE]; //为划分树,每一行代表树的一层
void build(int l,int r,int rt){
int i,mid,sum,lpos,rpos;
if(l==r)
return;
mid=(l+r)>>1;
sum=mid-l+1;
for(i=l;i<=r;i++) //划分树就是一颗依照快排的思想,从上而下建的树
if(tree[rt][i]<tmp[mid]) //因此每一个节点的左儿子中不可能存在比右儿子中
sum--; //大的数,依照这个原则建树
lpos=l,rpos=mid+1;
for(i=l;i<=r;i++){
if(tree[rt][i]<tmp[mid])
tree[rt+1][lpos++]=tree[rt][i];
else if(tree[rt][i]==tmp[mid]&&sum>0){
tree[rt+1][lpos++]=tree[rt][i];
sum--;
}
else
tree[rt+1][rpos++]=tree[rt][i];
toleft[rt][i]=toleft[rt][l-1]+lpos-l;
}
build(l,mid,rt+1);
build(mid+1,r,rt+1);
}
int query(int L,int R,int k,int l,int r,int rt){
int mid,newl,newr,cnt; //询问的时候如果区间内进入左树的数量大于
if(L==R) //k,那么第k个值一定在左树,否则则相当于在
return tree[rt][L]; //右树中找第k-cnt个值(cnt为当前区间进入左
mid=(l+r)>>1; //树的个数),按照这个原则将区间逐渐缩小
cnt=toleft[rt][R]-toleft[rt][L-1];
if(cnt>=k){
newl=l+toleft[rt][L-1]-toleft[rt][l-1];
newr=newl+cnt-1; //先出现的数一定会先进入左树或者右树
return query(newl,newr,k,l,mid,rt+1);
}
else{
newr=R+toleft[rt][r]-toleft[rt][R];
newl=newr-(R-L-cnt);
return query(newl,newr,k-cnt,mid+1,r,rt+1);
}
}
int main(){
int n,m,i,a,b,c;
while(scanf("%d%d",&n,&m)!=EOF){
memset(tree,0,sizeof(tree));
for(i=1;i<=n;i++){
scanf("%d",&tree[0][i]);
tmp[i]=tree[0][i];
}
sort(tmp+1,tmp+n+1);
build(1,n,0);
while(m--){
scanf("%d%d%d",&a,&b,&c);
printf("%d\n",query(a,b,c,1,n,0));
}
}
return 0;
}