二分距离
期末考试在即,紧张预习数据挖掘的 Capps 对如下问题十分感兴趣:
在一维空间中有点集 S S S 包含 n n n 个点,用什么算法能快速回答如下 q q q 次查询:
第 i i i 次查询给出点 p i p_i pi 和整数 k i k_i ki ,要求输出 S S S 中与点 p i p_i pi 距离第 k i k_i ki 近的点和 p i p_i pi 的距离。
距离:若点 u i u_i ui 坐标为 x i x_i xi , v i v_i vi 坐标为 y i y_i yi ,则定义点 u i u_i ui 与点 v i v_i vi 的距离为 ∣ x i − y i ∣ ∣x_i −y_i∣ ∣xi−yi∣ 。
输入描述:
第一行两个整数 , n , q ( 1 ≤ n , q ≤ 2 × 1 0 5 ) n,q (1≤n,q≤2×10^5 ) n,q(1≤n,q≤2×105) 表示点集 S 的大小和查询次数。
第二行 n n n 个整数,第 i i i 个整数 a i ( − 1 0 9 a_i(−10^{9} ai(−109 ≤ a i ≤ 1 0 9 ) ≤a_i ≤10^{9} ) ≤ai≤109)描述点集 S 里第 i i i 个点的坐标。
保证对于 ∀ i , j ( 1 ≤ i < j ≤ n ) 有 ≠ a i = a j ∀i,j (1≤i<j≤n) 有 ≠a_i =a_j ∀i,j(1≤i<j≤n)有=ai=aj 。接下来 q q q 行, 第 i i i 行两个整数 , x i x_i xi , k i k_i ki ( − 1 0 9 ≤ x i ≤ 1 0 9 , (−10^9 ≤x_i ≤10^9 , (−109≤xi≤109,
1 ≤ k i ≤ n ) 1≤k_i ≤n) 1≤ki≤n),表示 p i p_i pi 的坐标和需要查询距离 p i p_i pi 第 k i k_i ki 近的结果。
输出描述:
输出 q q q 行,第 i i i 行一个整数,表示第 i i i 次查询的答案。
这里可以有很多种想法,比如说取差值,二分找点,定义排序规则,但是本题给出的数据范围只允许 q q q 询问内不超过 O ( N ) O(N) O(N) 的时间复杂度。
补充:lower_bound( arr.begin , arr.end , aim )
返回数组中大于等于 a i m aim aim 的第一个元素的地址
upper_bound( arr.begin , arr.end , aim )
返回数组中大于 a i m aim aim 的第一个元素的地址
对于lower_bound( arr.begin , arr.end , aim ) - arr
,就代表了返回大于等于 a i m aim aim 的第一个元素的下标,upper_bound()
同理。
那么可以二分距离
在排序之后,首先在外层二分取距离( m i d mid mid ),也就是枚举距离给出 x x x 的距离,然后使用lower_bound
和upper_bound
去找到大于等于 x − m i d x-mid x−mid 的第一个数对应的下标和大于 x + m i d x +mid x+mid 的第一个数对应的下标,然后检查他们(下标)的差值是否是大于等于k的,如果是,就去二分更小的距离,直到确定最终的第k个数和x相差的距离。
这里二分的意义是找到满足 x − m i d x-mid x−mid 到 x + m i d x+mid x+mid 中间满足有 k k k 个数的最小的距离
把那些数都反映成数轴上的数,距离也是以 x x x 为中心向两边扩散,如果这个范围能够涵盖到 k k k 个点,那么就说明离 x x x 第 k k k 近的点一定就在他们其中。
在距离区间内的数的个数大于等于 k k k 的时候,就去搜更小的距离,也就是r = mid
,如果距离区间内的数的个数小于 k k k 了,那么就要从大于 m i d mid mid 的点开始继续二分,即l = mid + 1
,直到最后就会搜到一个最小的距离,使得其满足区间内有 k k k 个点。
搜到的这个距离一定保证是x距离某一个点的距离:因为如果你确定出来的是最小的包含 k k k 个数的距离,那么这个距离一定是刚刚好包含了 k k k 个数的,即第k近的数是在这个距离的边界上的。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
const int mod = 1e9+7;
#define ll long longint a[N];
ll x,k;
int n,q;bool check(int mid){int LL = lower_bound(a+1,a+1+n,x - mid) - a;int RR = upper_bound(a+1,a+1+n,(ll)x + mid) - a;if(RR - LL >= k)return 1;else return 0;
}int main(){cin >> n >> q;for(int i = 1;i <= n;i++)cin >> a[i];sort(a+1,a+1+n);while(q--){cin >> x >> k;int l = 0,r = 2e9;while(l < r){int mid = (ll)l +r >> 1;if(check(mid))r = mid;else l = mid + 1;}cout << r << endl;}
}
题解部分已经结束,但是如果跳出这道题,我们想要找到数组中距离某一个数第k近的数还可以有以下的办法。
定义排序规则
如果我们有一个数组a[]
,并且我们想要找到给定任意一个 x x x 值的第 k k k 近的数,那么我们可以定义一个按照数组中每个元素按照其与 x x x 的差值的大小进行排序的办法。此方法主要适用于sort()
函数
bool cmp(int a,int b){return abs(a - x) < abs(b - x);
}
双重二分+双指针
可以通过二分来找到距离 x x x 点最近的两个元素,之后取这两个元素中更接近于 x x x 的值为起点,使用双指针来向两边扩展。