题目
Description
对于一个整数序列,查询区间第k大数可以在O(logN)的时间内轻松完成。现在我们对这个问题进行推广。
考虑带重复数的集合(multiset)。定义在该类集合上的并操作“+”为两个集合的所有数不剔除重复得到的结果。比如,若A={1,2,2,3},B={2,3,4,4},则C={1,2,2,2,3,3,4,4}。
对于一个给定序列A[1…N],定义A[x…y]为包含y-x+1个元素的集合{A[x],A[x+1],…,A[y]}。现在,给定整数序列A,你需要回答很多询问,其中第i个询问要求集合A[x[i,1]…y[i,1]]+A[x[i,2]…y[i,2]]+…+A[x[i,ki]…y[i,ki]]中第pi小的元素。
Input
输入的第一行包含两个整数N和M,分别表示整数序列的长度和询问的个数。
第二行N个整数给出整数序列A。
第3到第M+2行给出了所有的询问。第i+2行前两个整数为ki和pi,接下来2ki个整数给出x[i, 1], y[i, 1], x[i,2], …, x[i, ki], y[i, ki]。这些数按照题目中的定义描述了第i个询问。
Output
对于每一个询问,输出相应的结果,即从小到大排序后的第pi个元素。
解题思路
把这道题目的查询修改即可,虽然zyc大佬说这很esay,但是我仍然理解了很久,可能是因为对主席树不太了解吧。
本来题目给出了几个 ( < = 5 ) (<=5) (<=5)的区间,我们可以将这些区间扔到主席树里面,分开求每个区间的值,然后累计起来,判断后继续分治。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define mid ((l+r)>>1)
#define rep(i,x,y) for(register int i=x;i<=y;i++)
using namespace std;
const int N=200010;
int n,q,m,cnt=0,a[N],b[N],t[N],sum[N<<5],L[N<<5],R[N<<5],xt[N],yt[N],z;
inline int build(int l,int r){
int rt=++cnt; sum[rt]=0;
if (l<r) L[rt]=build(l,mid),R[rt]=build(mid+1,r);
return rt;
}
inline int update(int pre,int l,int r,int x){
int rt=++cnt;
L[rt]=L[pre],R[rt]=R[pre],sum[rt]=sum[pre]+1;
if (l<r) {if (x<=mid) L[rt]=update(L[pre],l,mid,x); else R[rt]=update(R[pre],mid+1,r,x);}
return rt;
}
inline int query(int u[],int v[],int l,int r,int k){
if (l==r) return l; int g=0;
rep(i,1,z) g+=sum[L[v[i]]]-sum[L[u[i]]];
if (g>=k){
rep(i,1,z) v[i]=L[v[i]],u[i]=L[u[i]]; query(u,v,l,mid,k);
} else {
rep(i,1,z) v[i]=R[v[i]],u[i]=R[u[i]]; query(u,v,mid+1,r,k-g);
}
}
int main(){
scanf("%d%d",&n,&q);
rep(i,1,n) {scanf("%d",&a[i]); b[i]=a[i];}
sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1;
t[0]=build(1,m);
rep(i,1,n){
int k=lower_bound(b+1,b+m+1,a[i])-b;
t[i]=update(t[i-1],1,m,k);
}
int k;
while(q--){
scanf("%d%d",&z,&k);
rep(i,1,z) scanf("%d%d",&xt[i],&yt[i]),xt[i]=t[xt[i]-1],yt[i]=t[yt[i]];
printf("%d\n",b[query(xt,yt,1,m,k)]);
}
}