本期主要讲解二分查找。
知识点
二分查找:
-
思想:分治。
-
使用场景:在一个有序序列中,反复查找不同目标。
-
时间复杂度:\(O(n \log n)\)。
实现:
-
对数列排序;
-
确定二分边界(通常为
L=最小下标-1
,R=最大下标+1
); -
伪代码:
int L=左边界-1,R=右边界+1; while(L+1<R){ int mid=(L+R)>>1; if(目标在mid左侧) L=mid; else R=mid; }
-
最后根据
L
和R
的位置进行特判(不必要)。
例题
T1
板子题。
若要找出第一次出现的编号,则在 a[mid]==x
时还需要向前寻找。
所以只需将伪代码改一下就行。
注意最后输出的编号应为 R
,且需要特判 -1
的情况。
#include<bits/stdc++.h>
using namespace std;
int n,m,a[10000031];
int bs(int x){
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)>>1;
if(x<=a[mid]) r=mid;
else l=mid;
}
if(a[r]==x) return r;
return -1;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
while(m--){
int x; cin>>x; cout<<bs(x)<<' ';
}
return 0;
}
T2
对学校数组排序,遍历 \(n\) 位学生,找出和当前学生最接近的,比他的估分大 / 小的分数线,对两种不满意值取 \(\min\),并加入答案,最后输出即可。
需要注意的是,因为我们的 L
、R
的下标是无效的,所以若某学生的分数足够低,就可能出现 L
不动的情况,从而导致输出无效结果。因此我们应当将第 \(0\) 所学校的分数线设为 \(- \infty\),从而避免此类情况。
Tips:开 long long
。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int m,n,ans;
int a[100031],b[100031];
int bs1(int x){
int l=0,r=m+1;
while(l+1<r){
int mid=(l+r)>>1;
if(x<a[mid]) r=mid;
else l=mid;
}
return a[l];
}
int bs2(int x){
int l=0,r=m+1;
while(l+1<r){
int mid=(l+r)>>1;
if(x<=a[mid]) r=mid;
else l=mid;
}
return a[r];
}
signed main(){
cin>>m>>n;
for(int i=1;i<=m;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
a[0]=-1e9;
sort(a+1,a+m+1);
//cout<<bs(b[1]);
for(int i=1;i<=n;i++){
int x=bs1(b[i]),y=bs2(b[i]);
ans+=min(abs(x-b[i]),abs(y-b[i]));
}
cout<<ans;
return 0;
}
习题
T3
对于每个满足 \(1 \le i \le n\) 的 \(a_i\),二分查找 \(a\) 中第一个 \(\ge a_i+c\) 和 \(> a_i+c\) 的数,它们中间的就都是 \(a_i+c\),将其累加入 \(ans\) 中,最后输出 \(ans\) 即可。
如果你愿意,使用 STL lower_bound / upper_bound
将会快捷许多。
不开 long long
\(92 \ pts\),警钟撅烂。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,c,ans;
int a[200031];
int bs1(int x){
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)>>1;
if(x<=a[mid]) r=mid;
else l=mid;
}
if(a[r]==x) return r;
return -1;
}
int bs2(int x){
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)>>1;
if(x<=a[mid]) r=mid;
else l=mid;
}
if(a[l]==x) return l;
return -1;
}
signed main(){
cin>>n>>c;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
if(bs1(a[i]+c)!=-1&&bs2(a[i]+c)!=-1)
ans+=bs2(a[i]+c)-bs1(a[i]+c)+1;
cout<<ans;
return 0;
}
T4
和 T2 很像,只需要把那两个函数搬过来,在重写一下输入和输出即可。
我这里把两个函数合并到一块了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int m,n,ans;
int a[100031],b[100031];
int bs(int x){
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)>>1;
if(x<=a[mid]) r=mid;
else l=mid;
}
if(abs(a[l]-x)<abs(a[r]-x)) return a[l];
if(abs(a[l]-x)>abs(a[r]-x)) return a[r];
if(abs(a[l]-x)==abs(a[r]-x)) return min(a[l],a[r]);
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
a[0]=-1e9;
cin>>m;
for(int i=1,x;i<=m;i++) cin>>x,cout<<bs(x)<<'\n';
return 0;
}