1.二分查找
- 使用条件:有序数组
- 复杂度:O(log2n)
- 思路:对于一个有序数组(以升序数组为例),将要找的数与数组最中间的数进行比较,如果如果这个数比中间数大,那么这个数肯定在数组的后半部分,反之亦然。如此每次都能将数据总数折去一半,十分高效。
- 代码:
bool find(int x)
{
int begin = 1,end = MAXN;
while(begin <= end)
{
int mid = (begin + end) / 2;
if(arr[mid] > x)
end = mid - 1;
else if(arr[mid] < x)
begin = mid + 1;
else
return 1;
}
return 0;
}
2.二分查找重复数据
- 上面的代码实现了查找arr数组内是否存在x这个元素,那么如果arr内有多个x,能否用二分查找找第一个或最后一个元素x呢?答案是显然的。
- 针对寻找第一个数,如果找到这个数就先前找,即执行
那么最后end指向的肯定是第一个数的前一个,begin指向第一个数.end = mid - 1
- 针对寻找最后一个数,如果找到这个数就向后找,即执行
那么最后begin指向的肯定是最后一个数的后一个,end指向最后一个数.begin = mid + 1
- 代码:
int find_begin(int x) { int begin = 1,end = MAXN; while(begin <= end) { int mid = (begin + end) / 2; if(arr[mid] >= x) end = mid - 1; else begin = mid + 1; } return begin; } int find_end(int x) { int begin = 1,end = MAXN; while(begin <= end) { int mid = (begin + end) / 2; if(arr[mid] > x) end = mid - 1; else begin = mid + 1; } return end; }
- 如果找到了第一个数和最后一个数,两者相减再加1便是此数在arr数组中出现的次数
- 代码:
int number(int x) { return find_end(x) - find_begin(x) + 1; }
3.二分答案:
- 我们将答案的所有可能的解看作一个集合,如果判断一个成立或不成立可以排除掉比它大或小的答案,并且解有一个限定的范围,那么就可以对答案进行二分查找来找出最优解
- 例1:一元三次方程求解:
众所周知,一元三次方程可以写作y = (x - x1)(x - x2)(x - x3)
那么在x1,x2,x3的左右两端很小范围内的函数值一定是异号的,如此就可以逐步缩小范围
- 既然根的范围在-100到100之间,并且每个根之间相差至少为1,那么我们就可以遍历-100~-99,-99~-98......99~100的区间来找到所有的根
- 缩小区间:取i 与 i + 1的中位数mid,如果f(i) * f(mid) < 0说明根在i与mid之间,否则在mid与i + 1之间即:
if(f(begin) * f(mid) < 0) end = mid; else begin = mid;
- 当然,我们还要考虑特殊情况:当方程的根为整数时,上面的方式就会重复输出那个整数根,因此需要特判,因为0可以被浮点数精准表示出来所以不需要用到浮点数判断相等的方法.
代码:
#include<bits/stdc++.h>
using namespace std;
double a,b,c,d;
double f(double x)
{
return a*x*x*x + b*x*x + c*x + d;
}
int find(int i)
{
double begin = i,end = i + 1;
if(f(begin) * f(end) > 0)
return 0;
else if(abs(f(end)) == 0)
{
printf("%.2lf ",end);
return 1;
}
else if(abs(f(begin)) == 0)
{
return 0;
}
while(end - begin > 0.005)
{
double mid = (begin + end) / 2;
if(f(begin) * f(mid) < 0)
end = mid;
else
begin = mid;
}
printf("%.2lf ",begin);
return 0;
}
int main()
{
cin >> a >> b >> c >> d;
for(int i = -100;i <= 99;i++)
{
find(i);
}
}
我们再来一个应用领域的题目
- 例2:洛谷P3853
- 可能的答案一定比公路的长度短因此可能答案的集合为0~10000000(对计算机来说只是洒洒水啦)
- 首先,空旷程度越大,它越可能成立,如果这个可能的答案能成立,那么就需要向越不可能成立的方向继续判断,即向小的方向判断,即:
ll mid = (begin + end) / 2; if(panduan(mid)) end = mid - 1; else begin = mid + 1;
- 然后panduan函数用于判断mid的空旷程度能否成立,也就是让它所需要插的最少路标与最多可增设的路标K进行比较,即:
bool panduan(int i) { int sum = 0; for(int j = 1;j <= N;j++) { if(d[j] - d[j - 1] <= i)continue; sum += (d[j] - d[j - 1]) / i; if((d[j] - d[j - 1]) % i == 0) sum--; } return sum <= K; }
- 然后我们就做出来了,代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll MAX = 110000; ll d[MAX],L,N,K; bool panduan(int i) { ll sum = 0; for(int j = 1;j <= N;j++) { if(d[j] - d[j - 1] <= i)continue; sum += (d[j] - d[j - 1]) / i; if((d[j] - d[j - 1]) % i == 0) sum--; } return sum <= K; } int main() { cin >> L >> N >> K; for(ll i = 1;i <= N;i++) cin >> d[i]; ll begin = 1,end = 10000000; while(begin <= end) { ll mid = (begin + end) / 2; if(panduan(mid)) end = mid - 1; else begin = mid + 1; } printf("%lld",begin); }
结束
-