链接:点击打开链接
题意:给定一个数列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; }