[题解]一本通1240:查找最接近的元素
查找最接近的元素
[题目描述]
在一个非降序列中,查找与给定值最接近的元素。
[输入]
第一行包含一个整数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
[思路]
本题数据比较大,如果枚举最坏情况是m*n=10000*100000=1*10^9,会超时,那么我们就要用到二分。二分的时间复杂度大概是O(m*log(n)),并且二分是建立在有序元素之上的,题目中已经说"为非降序列各元素"所以不用排序,但我当时没有看见所以加了sort,但也没有超时。所以直接套用二分模板得到了代码1:
[代码(60分)]
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 using namespace std; 8 unsigned long long date[100010]; 9 unsigned long long myfind(int left,int right,unsigned long long a){ 10 if(left+1==right){ 11 return (a-date[left])<(date[right]-a)?date[left]:date[right];/* 12 相当于if((a-date[left])<(date[right]-a)){ 13 return date[left]; 14 } 15 else { 16 return date[right]; 17 } 18 */ 19 } 20 int middle=(left+right)/2; 21 if(date[middle]>a){ 22 return myfind(left,middle,a); 23 } 24 else { 25 return myfind(middle,right,a); 26 } 27 } 28 int main(){ 29 int n; 30 scanf("%d",&n); 31 for(int i=0;i<n;++i){ 32 scanf("%u",&date[i]); 33 } 34 sort(date,date+n);//此行可以没有 35 int m; 36 scanf("%d",&m); 37 for(int i=0;i<m;++i){ 38 unsigned long long a; 39 scanf("%u",&a); 40 printf("%u\n",myfind(0,n,a)); 41 } 42 return 0; 43 }
自己手头试了几个数据发现在有两种解是总是输出较大的与题目不符于是把return (a-date[left])<(date[right]-a)?date[left]:date[right];改成了return (a-date[left])<=(date[right]-a)?date[left]:date[right];即如果有相同解返回较小的数。于是产生了代码2:
[代码(70分)]
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 using namespace std; 8 unsigned long long date[100010]; 9 unsigned long long myfind(int left,int right,unsigned long long a){ 10 if(left+1==right){ 11 return (a-date[left])<=(date[right]-a)?date[left]:date[right]; 12 }//<=可以把两个解中取较小值 13 int middle=(left+right)/2; 14 if(date[middle]>=a){ 15 return myfind(left,middle,a); 16 } 17 else { 18 return myfind(middle,right,a); 19 } 20 } 21 int main(){ 22 int n; 23 scanf("%d",&n); 24 for(int i=0;i<n;++i){ 25 scanf("%u",&date[i]); 26 } 27 sort(date,date+n); 28 int m; 29 scanf("%d",&m); 30 for(int i=0;i<m;++i){ 31 unsigned long long a; 32 scanf("%u",&a); 33 printf("%u\n",myfind(0,n-1,a)); 34 } 35 return 0; 36 } 37 /* 38 2 39 6 10 40 1 41 8 42 10 43 */
这就尴尬了,为什么还不对?于是找了个标程对拍(ps:这是我第一次写对拍,只知道原理,没想到很快居然写出来了,第一次写所以文件名都没有太注意(out1是我的,out2是标程,in.in是输入数据。这是关于对拍的链接:对拍)发现:
可以发现如果要查找的元素比数据中最小的元素还小return (a-date[left])<=(date[right]-a)?date[left]:date[right];就会导致小数减大数又因为是unsigned long long从而导致运算错误。于是加了一个特判如果查找的元素比数据中最小的元素还小返回数据中最小的元素。得到了代码3:
[代码(AC)]
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 using namespace std; 8 unsigned long long date[100010]; 9 unsigned long long myfind(int left,int right,unsigned long long a){ 10 if(left+1==right){ 11 if(a<date[left]){ 12 return date[left]; 13 }//特判:防止寻找元素小于最小元素 14 return (a-date[left])<=(date[right]-a)?date[left]:date[right]; 15 } 16 int middle=(left+right)/2; 17 if(date[middle]>=a){ 18 return myfind(left,middle,a); 19 } 20 else { 21 return myfind(middle,right,a); 22 } 23 } 24 int main(){ 25 //freopen("in.in","r",stdin); 26 //freopen("out1.out","w",stdout); 27 int n; 28 scanf("%d",&n); 29 for(int i=0;i<n;++i){ 30 scanf("%u",&date[i]); 31 } 32 sort(date,date+n); 33 int m; 34 scanf("%d",&m); 35 for(int i=0;i<m;++i){ 36 unsigned long long a; 37 scanf("%u",&a); 38 printf("%u\n",myfind(0,n,a)); 39 } 40 return 0; 41 } 42 /* 43 10 44 5399 45 14104 46 2296 47 19615 48 29450 49 5146 50 31395 51 5577 52 5189 53 13437 54 10 55 7809 56 26463 57 826 58 27322 59 26548 60 26693 61 2635 62 23163 63 8914 64 19465 65 */
2018-11-30 23:39:19