二分查找与二分答案
前言
二分查找是在有序的序列的基础上的一种非常高效的查找方法。
二分答案可以用于这样一类题目:
- 正着进行枚举非常麻烦,而且时空复杂度很高。
- 对于某一种情况是否符合判断相对容易
例题
P2249 【深基13.例1】查找
题目描述
输入 n 个不超过10^9的单调不减的(就是后面的数字不小于前面的数字)非负整数a*1,a2,…,a n,然后进行 m 次询问。对于每次询问,给出一个整数 q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 −1 。
输入格式
第一行 2 个整数 n 和 m,表示数字个数和询问次数。
第二行 n 个整数,表示这些待查询的数字。
第三行 m 个整数,表示询问这些数字的编号,从 1 开始编号。
输出格式
输出一行,m 个整数,以空格隔开,表示答案。
输入输出样例
输入 #1复制
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
输出 #1复制
1 2 -1
说明/提示
数据保证,1≤n≤10^6,0≤a i,q≤109,1≤*m*≤105
本题输入输出量较大,请使用较快的 IO 方式。
思路
本题已经出现了单调区间,属于二分查找题目,可以算是最朴素的板子题
代码实现
#include<iostream>
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
int a[1000010]={};
for(int i=1;i<=n;i++) cin>>a[i];
while(m--)
{
int number;
int flag=0;
cin>>number;
int i=1;
int j=n;
while(i<=j)
{
int mid=(i+j)/2;
if(a[mid]==number) flag=1;
if(a[mid]<number) i=mid+1;
else j=mid-1;
}
if(flag) cout<<i<<" ";
else cout<<"-1 ";
}
return 0;
}
P1824 进击的奶牛
题目描述
Farmer John 建造了一个有 N(2 ≤ N ≤100000) 个隔间的牛棚,这些隔间分布在一条直线上,坐标是 x1 ,…,x N (0 ≤x i ≤ 1000000000)。
他的 C(2 ≤ C ≤ N) 头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?
输入格式
第 1 行:两个用空格隔开的数字 N 和 C。
第 2 ~ N+1 行:每行一个整数,表示每个隔间的坐标。
输出格式
输出只有一行,即相邻两头牛最大的最近距离。
输入输出样例
输入 #1
5 3
1
2
8
4
9
输出 #1
3
思路
本题是典型的二分答案,对于直接枚举距离,时间复杂度较高,但是如果从答案出发进行二分,就能很好的解决这类问题
代码实现
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int a[maxn]={};
int n,c;
bool pd(int mid)
{
int sum=1;
int now=a[1];
for(int i=2;i<=n;i++)
{
if(a[i]-now>=mid) sum++,now=a[i];
}
if(sum>=c) return true;
return false;
}
int main()
{
cin>>n>>c;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
int l=1,r=a[n]-a[1];
int mid;
while(l<=r)
{
mid=(l+r)/2;
if(pd(mid)) l=mid+1;
else r=mid-1;
}
cout<<r<<endl;
return 0;
}
小结
二分的思想在之后并不会但出现,而是以插曲的形式出现,解决一些细节,因此掌握二分的思想比记住模版更重要。
二分查找代码能够在有序序列的基础上大大的减少查找的时间复杂度,因此在明显超时的部分情况,可以考虑使用二分。
二分答案其实更趋向于正难则反的思想,从结果出发,利用二分时间复杂度小的特点,对结果进行高效率的枚举。