(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
5if(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; }