2019杭电暑期多校第四场H - K-th Closest Distance (二分+主席树)

以下均学习于这个老哥的博客https://blog.youkuaiyun.com/qq_41286356/article/details/97958401#commentsedit

题意:给定你N个数字, Q次询问,l,r,p,k,问你在【L,R】区间内的离P第K近的数的距离,强制在线

题解:观察发现可以二分距离,又因为主席树是可持久化的线段树,边更新边建树,最后直接询问即可。 每次查询【L,R】区间内【p-mid,p+mid】有多少数即可。  所以主席树也可以处理[L,R]区间里[l,r]里数的个数

---------------------------------------------------

细节:刚开始不是特别理解    这块我认为只找到了覆盖【pl,pr】的结点怎么能直接返回【L,R】的数的个数那,不是就多了吗

if(pl<=l&&r<=pr) return T[R].sum-T[L].sum;

,后来问了内个老哥才彻底懂了,因为主席树是可持久化的权值线段树,每一个结点都是一个前缀线段树。pl,pr是这棵主席树上查找的权值区间,L和R才是主席树上某两颗线段树,所以在【pl,pr】的结点返回的就是这两颗前缀权值树的差(【L,R】)就是当前区间数的个数。

另一个问题就是 对数据离散化后,二分的p+mid,p-mid也得离散化,需要注意的就是我每次查询都是【p+mid,p-mid】闭区间内

	int PL=lower_bound(v.begin(),v.end(),p-mid)-v.begin()+1;
	int PR=upper_bound(v.begin(),v.end(),p+mid)-v.begin();

一定得保证这个区间内,所以PL找大于等于的第一个,PR找大于的第一个-1的位置即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
vector<int> v;
struct node{
	int l;
	int r;
	int sum;
}T[maxn*50];
int n,m,a[maxn],root[maxn],cnt;
int getid(int x){
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void update(int l,int r,int &x,int y,int pos){
	T[++cnt]=T[y];
	T[cnt].sum++; 
	x=cnt;      
	if(l==r)  return ;
	int mid=(l+r)/2;
	if(pos<=mid) update(l,mid,T[x].l,T[y].l,pos);
	else update(mid+1,r,T[x].r,T[y].r,pos);
}
int query(int l,int r,int L,int R,int pl,int pr){
	if(pl<=l&&r<=pr) return T[R].sum-T[L].sum;
	int mid=(l+r)/2;
	int ans=0;
	if(pl<=mid) ans+=query(l,mid,T[L].l,T[R].l,pl,pr);
	if(pr>mid)  ans+=query(mid+1,r,T[L].r,T[R].r,pl,pr);
	return ans;
}
int main(){
	std::ios::sync_with_stdio(false);
	int t;
//	cin>>t;
	scanf("%d",&t);
	while(t--){
	    //cin>>n>>m;
	    cnt=0; 
		v.clear();
	    scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),v.push_back(a[i]);
		sort(v.begin(),v.end());
		v.erase(unique(v.begin(),v.end()),v.end());
		int sz=v.size();
		for(int i=1;i<=n;i++){
			update(1,n,root[i],root[i-1],getid(a[i]));
		}
		int ans=0; 
		while(m--){
			int l,r,p,k;// cin>>l>>r>>p>>k; 
			scanf("%d%d%d%d",&l,&r,&p,&k);
			l=l^ans;r=r^ans;p=p^ans;k=k^ans;
			if(l>r) swap(l,r);
			int L=0;int R=1000005;
			while(L<=R){
				int mid=(L+R)/2;
				int PL=lower_bound(v.begin(),v.end(),p-mid)-v.begin()+1;
				int PR=upper_bound(v.begin(),v.end(),p+mid)-v.begin();
				int tmp=query(1,sz,root[l-1],root[r],PL,PR);
				if(tmp>=k){
					ans=mid;
					R=mid-1;
				}else L=mid+1;
			}
			//cout<<ans<<endl;
			printf("%d\n",ans);
		}
	}
} 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值