华南理工大学程序设计竞赛 - A-KNN算法 (二分)

二分距离

期末考试在即,紧张预习数据挖掘的 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​∣ xiyi

输入描述:
第一行两个整数 , n , q ( 1 ≤ n , q ≤ 2 × 1 0 5 ) n,q (1≤n,q≤2×10^5 ) n,q(1n,q2×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} ) ai109)描述点集 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(1i<jn)=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 , (109xi109,

1 ≤ k i ≤ n ) 1≤k_i ≤n) 1kin),表示 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_boundupper_bound去找到大于等于 x − m i d x-mid xmid 的第一个数对应的下标和大于 x + m i d x +mid x+mid 的第一个数对应的下标,然后检查他们(下标)的差值是否是大于等于k的,如果是,就去二分更小的距离,直到确定最终的第k个数和x相差的距离。

这里二分的意义是找到满足 x − m i d x-mid xmid 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 long

int 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 的值为起点,使用双指针来向两边扩展。

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值