题意:给一个数组,每次给 l ,r, p, k,问区间 [l, r] 的数与 p 作差的绝对值的第 k 小,这个绝对值是多少
分析:首先我们先分析单次查询怎么做:
题目给出的数据与多次查询已经在提示着我们在用数据结构去解决这个问题,对于常见的处理区间的数据结构首选线段树啦:
我觉得这道题的关键在于此:我们需要去二分答案ans, 为什么呢? 我们这样观察 ,对于 | p-a[i] | <= ans 等于 p-ans<=a[i] <=p+ans 那问题就转化为查询[L,R] 区间里面在[p-ans,p+ans] 范围的a[i] 有多少个 。这显然是到主席树的题目;
我们明白主席树的原理是多颗权值线段树 , 那我们就把a[i]当成下标,权值为出现的次数,那我们就是查询[L,R] 编号是权值线段树里面[p-ans,p+ans] 范围的a[i] 有多少个 。
主席树:
#include<bits/stdc++.h> using namespace std; const int N=1e6+10; int tot; int lson[N],rson[N],sum[N],tr[N]; void build(int &rt,int l,int r){ rt=++tot; if(l==r) return ; int mid=(l+r)>>1; build(lson[rt],l,mid); build(rson[rt],mid+1,r); } void updata(int root,int &rt,int p,int l,int r){ rt=++tot; lson[rt]=lson[root],rson[rt]=rson[root]; sum[rt]=sum[root]+1; if(l==r) return ; int mid=(l+r)>>1; if(p<=mid) updata(lson[root],lson[rt],p,l,mid); else updata(rson[root],rson[rt],p,mid+1,r); } int query(int rt_,int rt,int L,int R,int l,int r){ if(l<=L&&R<=r){ return sum[rt_]-sum[rt]; } int mid=(L+R)>>1; int ans=0; if(l<=mid) ans+=query(lson[rt_],lson[rt],L,mid,l,r); if(mid<r) ans+=query(rson[rt_],rson[rt],mid+1,R,l,r); return ans; } int main(){ int _;scanf("%d",&_); while(_--){ int n,m;scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { int x;scanf("%d",&x); updata(tr[i-1],tr[i],x,1,N); } int T=0; for(int i=1;i<=m;i++){ int L,R,p,k; scanf("%d%d%d%d",&L,&R,&p,&k); L^=T,R^=T,p^=T,k^=T; int LL=0,RR=1e6,ans=0; while(LL<=RR){ int mid=(LL+RR)>>1; int l=max(1,p-mid); int r=min(N,p+mid); if(query(tr[R],tr[L-1],1,N,l,r)>=k){ RR=mid-1;ans=mid; } else LL=mid+1; } T=ans; printf("%d\n",ans); } } return 0; }