(http://noi.openjudge.cn/ch0111/01/)

总时间限制: 
1000ms
 
内存限制: 
65536kB
描述

在一个非降序列中,查找与给定值最接近的元素。

输入
第一行包含一个整数n,为非降序列长度。1 <= n <= 100000。
第二行包含n个整数,为非降序列各元素。所有元素的大小均在0-1,000,000,000之间。
第三行包含一个整数m,为要询问的给定值个数。1 <= m <= 10000。
接下来m行,每行一个整数,为要询问最接近元素的给定值。所有给定值的大小均在0-1,000,000,000之间。
输出
m行,每行一个整数,为最接近相应给定值的元素值,保持输入顺序。若有多个值满足条件,输出最小的一个。
样例输入
3
2 5 8
2
10
5
样例输出
8
5

分析:题目可以采用二分算法来解决。
温馨提示:(1)如果想把程序写会,必须先有正确的思路。可以在纸上手动模拟一下,虽说程序是在计算机上实现,但是在解题的过程中,纸和笔都是必不可少的。
(2)对于二分算法如何验证程序的正确性:首先验证最大和最小值,然后验证中间的元素;能找到的情况,找不到的情况等等。再次试一下只有一个、二个、3个元素的情况,相等不相等,处于中间的情况等,都尝试一下。

(3)二分一定要确定:找不到的时候停在那个位置?否则程序好像看起来很对,但往往会出各种意想不到的错误。
参考程序一:递归版本,[lift,right),边递归边比较,浪费时间版
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int a[100100],minx=2000000000;
int solve(int left,int right,int x){
    int mid=(left+right)/2;
    if (x==a[mid]) return a[mid];
    if (abs(a[mid]-x)<abs(minx-x)||abs(a[mid]-x)==abs(minx-x)&&a[mid]<minx) minx=a[mid];//每次都和中间值比较,记录符合要求的值,偷懒的办法,其实只需要当只区间剩一个元素,或者没有元素时再比较更好,节省时间。
    if (x<a[mid]&&left<mid) return solve(left,mid,x);
    if (x>a[mid]&&mid+1<right)  return solve(mid+1,right,x) ;
    return minx;
}
int main(){
    int n,m;
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i] ;
    cin>>m;
    for(int i=0;i<m;i++){
        int x;
        cin>>x;
        cout<<solve(0,n,x)<<endl;
    }
    return 0;
}

 

参考程序二:递归版本,[lift,right),边递归边比较,时间版
#include<cstdio>
#include<iostream>
using namespace std;
int a[100010];
int n,m;
int Find(int l,int r,int x){   //[l,r)
    if(l==r)return l;//此时,a[l]<x<a[l+1],没有元素和x相等。
    int mid=(l+r)/2;
    if(a[mid]==x) return mid;//如果找到x直接返回下标
    if(a[mid]>x) return Find(l,mid,x);
    else return Find(mid+1,r,x);
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    cin>>m;
    for(int i=0;i<m;i++){
        int x;
        cin>>x;
        if(x<=a[0])cout<<a[0]<<endl;//x是最小值,最小值和最大值容易在比较中越界,比如k-1=-1,k=n等,所以单独处理。
        else 
             if(x>=a[n-1])cout<<a[n-1]<<endl;//x是最大值

else{ int k=Find(0,n,x);
//cout<<k<<endl; //比较x-a[k-1]与a[k]-x

if(x-a[k-1]<=a[k]-x)cout<<a[k-1]<<endl; else cout<<a[k]<<endl; //a[k-1]<=x<=a[k]
}
}
return 0;
}

参考程序三:while版本,[lift,right),这个比递归更好一些,节约空间。

//http://noi.openjudge.cn/ch0111/01/ 
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n;
int a[100100],minx=2000000000;
int solve(int x){//左闭右开 
    if (x<=a[0])return a[0];
    if (x>=a[n-1])return a[n-1];
    int left=0,right=n;
    while (left<right){
        int mid=(left+right)/2;
        if (x==a[mid]) return a[mid];
        if (x<a[mid]) right=mid;
        else left=mid+1;        
        }
    return x-a[left-1]<=a[left]-x?a[left-1]:a[left];
}
int main(){
    int m;
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i] ;
    cin>>m;
    for(int i=0;i<m;i++){
        int x;
        cin>>x;
        cout<<solve(x)<<endl;
    }
    return 0;
}

 

参考程序四:递归1[l,r]
递归会在" 第一个大于x的位置, 如果多个和x相等,应停留在最左边的位置。"
#include<cstdio>
#include<iostream>
using namespace std;
int a[100010];
int n,m;
int Find(int l,int r,int x){
    //查找满足a[k]>=x最小的位置k 
    if(l==r)return l;
    int mid=(l+r)/2;
    if(a[mid]>=x) return Find(l,mid,x);
    else return Find(mid+1,r,x);
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    cin>>m;
    for(int i=0;i<m;i++){
        int x;
        cin>>x;
        int k=Find(0,n-1,x);
        //cout<<k<<endl;
        //比较x-a[k-1]与a[k]-x 
        if(k==0)cout<<a[0]<<endl;
        else
            if(x-a[k-1]<=a[k]-x)cout<<a[k-1]<<endl;
            else cout<<a[k]<<endl;
    }
    return 0;
}
参考程序五:递归2[l,r]
数据:
4
2 4 6 8
1
5
if(a[mid]>x) return Find(l,mid-1,x)时过不了?
具体分析:
L R mid 值
0 3 1 4
2 3 2 6
2 1 1 4
没有经历l==r,直接就l<r了


#include<cstdio>
#include<iostream>
using namespace std;
int a[100010];
int n,m;
int Find(int l,int r,int x){
    //查找满足a[k]>=x最小的位置k 
    if(l==r)return l;
    int mid=(l+r)/2;
    if(a[mid]==x) return mid;
    if(a[mid]>x) return Find(l,mid,x);//这里如果改成【l,mid-1】程序反而不对了,因为mid-1有可能小于l;但是现在mid,mid+1无缝衔接就没有问题了,保留mid就是为了防止找不到时,x掉在空挡里的情况。
    else return Find(mid+1,r,x);
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    cin>>m;
    for(int i=0;i<m;i++){
        int x;
        cin>>x;
        int k=Find(0,n-1,x);
        //cout<<k<<endl;
        //比较x-a[k-1]与a[k]-x 
        if(k==0)cout<<a[0]<<endl;
        else
            if(x-a[k-1]<=a[k]-x)cout<<a[k-1]<<endl;
            else cout<<a[k]<<endl;
    }
    return 0;
}

参考程序六:while循环[lift,right]
#include<cstdio>
#include<iostream>
using namespace std;
int a[100000+100];
int n,m;
int find(int x){
    int l,r;
    l=0;r=n-1;
    while(l<r){
        int mid=(l+r)/2;
        if(a[mid]>=x) r=mid;//找离x最近的那个最小的数 
        else l=mid+1;
    }
    if (l==0) return 0;    //如果找的值是所有数中的最小值,则直接返回最小下标0。
    return x-a[l-1]<=a[l]-x?l-1:l;//循环结束时l=r, 此时a[l]>=x,a[l-1]<x,从两者中找出一个符合题意的 
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    cin>>m;
    for(int i=0;i<m;i++){
        int x,p;
        cin>>x;
        p=find(x);
        cout<<a[p]<<endl;
    }
    return 0;
}