二分查找与二分答案的应用

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 = mid - 1
    那么最后end指向的肯定是第一个数的前一个,begin指向第一个数.
  • 针对寻找最后一个数,如果找到这个数就向后找,即执行
    begin = mid + 1
    那么最后begin指向的肯定是最后一个数的后一个,end指向最后一个数.
  • 代码:
    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的左右两端很小范围内的函数值一定是异号的,如此就可以逐步缩小范围

洛谷P1024

  •  既然根的范围在-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);
	}
}

我们再来一个应用领域的题目

  •  可能的答案一定比公路的长度短因此可能答案的集合为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);
    }

    结束

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值