【传送门:caioj1441】
简要题意:
给出一个n个数的序列,m个询问,每个询问输入l,r,k,输出第l个数到第r个数第k小的数
题解:
首先想到线段树,但是做不到询问区间的第几小,只能做到最大或最小或和
所以我们用权值线段树——主席树来解决这道题
参考代码:
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct node { int lc,rc,c;//c维护的是这个区间维护了多少个节点 }tr[2100000];int len; int n,m; int a[110000],s[110000],root[110000]; int LS(int d) { int l=1,r=n,ans=0; while(l<=r) { int mid=(l+r)/2; if(s[mid]<=d) { ans=mid; l=mid+1; } else r=mid-1; } return ans; } void Link(int &u,int l,int r,int p) { if(u==0) u=++len; tr[u].c++; if(l==r) return ; int mid=(l+r)/2; if(p<=mid) Link(tr[u].lc,l,mid,p); else Link(tr[u].rc,mid+1,r,p); } void Merge(int &u1,int u2) { if(u1==0){u1=u2;return ;} if(u2==0) return ; tr[u1].c+=tr[u2].c; Merge(tr[u1].lc,tr[u2].lc); Merge(tr[u1].rc,tr[u2].rc); } int Calc(int u1,int u2,int l,int r,int k) { if(l==r) return s[l]; int c=tr[tr[u1].lc].c-tr[tr[u2].lc].c;//用u1这棵线段树减u2这棵线段树就可以得到u1到u2这段区间的信息 int mid=(l+r)/2; if(k<=c) return Calc(tr[u1].lc,tr[u2].lc,l,mid,k); else return Calc(tr[u1].rc,tr[u2].rc,mid+1,r,k-c); } int main() { len=0; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); s[i]=a[i]; } sort(s+1,s+n+1); memset(root,0,sizeof(root)); for(int i=1;i<=n;i++) { Link(root[i],1,n,LS(a[i]));//每次插入以rt[i]为根一条链这条链维护i这个点的信息 Merge(root[i],root[i-1]);//将其与前i-1条链合并,这样rt[i]为根的这条链维护的就是1~i的信息 } for(int i=1;i<=m;i++) { int x,y,k; scanf("%d%d%d",&x,&y,&k); printf("%d ",Calc(root[y],root[x-1],1,n,k)); } return 0; }