二分

本文深入解析二分法的原理与优势,通过多个实例展示其在数据查找、函数最值及复杂问题求解中的应用,代码实现详尽,助你掌握高效算法。

二分

二分思想,是一种重要的思想,它的优点在于,对于大量数据处理的时候,我们能有效地简化算法,减少时间复杂度,提高效率。
以我们的二分查找为例,在一个有序数列中查找某个数的下标,以我们以前的方法,遍历整个数列,直到找到这个数为止,如果数据量小的话,这个方法是可行的,但是到了上亿甚至更大的数据量,这个算法就过于复杂。我们这时就可以用二分查找的方法,所谓二分查找,以此题为例(升序),我们先找到所有数最中间的那个数,与我们的目标数进行比较,如果目标数大于这个中间数,就在中间数到最后一个数之间再找一个中间数,与目标数比较,复杂度是logn,由于是指数级的增长,大大减少了比较次数,可能上亿的数据量我们只需要比较几十次就可以达到目的,这就是二分的核心思想。
先看一个简单的例题:
现有 N 个大理石,每个大理石上写了一个非负整数。首先请你将这 N 块大理石按照它们上面所写数字的大小从小到大排序,然后按照排序后的顺序把这 N 个大理石从 1 ~ N 编号。之后请你回答 Q 个询问,每个询问问是否存在一个大理石上写着某个整数 x,如果存在,请你回答写着整数 x 的编号最小的大理石的编号,如果不存在,请你输出 x not found。
有多组数据。
每组数据第一行包括两个整数 N,Q,当输入为 N = 0, Q = 0 时,表示结束。
否则接下来 Q 行,每一行包括一个数字 x,表示询问。
Sample input
4 1
2
3
5
1
5
5 2
1
3
3
3
1
2
3
0 0
Sample Output
CASE# 1:
5 found at 4
CASE# 2:
2 not found
3 found at 3
这个题目过于简单了,直接看代码:

#include<iostream>
#include<algorithm>
using namespace std;
int a[10010], b[10010];
int main() 
{
	int n, q, num=1, l, r, m;
	while(cin >> n >> q)
	{
		if(n==0&&q==0)
		return 0;
		for(int i = 1; i <= n; i++)
		cin >> a[i];
		for(int i = 1; i <= q; i++)
		cin >> b[i];
		sort(a+1,a+n+1);
		printf("CASE# %d:\n",num++);
		for(int i = 1; i <= q; i++)
		{
			l = 1, r = n, m = (l+r)/2;
			while(1)
			{
				if(b[i] <= a[m])
				{
					r = m;
					if(r == l + 1)
					{
						if(b[i] == a[l])
						{
							cout << b[i] <<" found at " << l <<endl;
							break;
						}
						else if(b[i] == a[r])
						{
							cout << b[i] <<" found at " << r << endl;
							break;
						}
						else
						{
							cout << b[i] <<" not found" << endl;
							break;
						}
					}
				}
				else
				{
					l = m;
					if(r == l + 1)
					{
						if(b[i] == a[l])
						{
							cout << b[i] <<" found at " << l <<endl;
							break;
						}
						else if(b[i] == a[r])
						{
							cout << b[i] <<" found at " << r << endl;
							break;
						}
						else
						{
							cout << b[i] <<" not found" << endl;
							break;
						}
					}
				}
				m = (l + r)/2;
			}
		} 
	}
}

我们再来看一个二分来解决函数的最值问题的例题:
某学长最近在玩一款名为“我的世界”的一款游戏。众所周知,这个游戏中有很多水池,今天学长出门寻宝的过程中发现了一个水池,水池的水位在不断的变化,他想了解一下这个水池的水位是如何变化的,他去”游戏百科“中搜了一下,果然”游戏百科“真强大,他了解到水位是根据: F(x) = 6x^7 +8x^6 +7x^3 +5x^2-y*x (0 <= x <=100) 变化的,其中y值是学长的游戏人物的生命值。学长想知道当自己游戏人物的生命值一定时,F(x)的最小值(0 <= x <=100)
input
第一行包含一个整数Y(1<=T<=100) ,代表测试样例的个数。接下来输入包含T行,每行一个实数y,代表当前学长游戏人物的生命值(0 < y <1e10)。
output
输出一行包含一个实数F(x)(0 <= x <=100),代表最低水位,保留4位小数
Sample Input
2
100
200
Sample Output
-74.4291
-178.8534
就是一个求最小值问题啦,高中数学知识,我们先求导,如果导数恒正或者恒负,那么就在端点处取得最小值,由于在此题中导数的变化是递增的,排除前面两种情况之后就只能是导数为零的点处取得最小值,那么就可以用二分查找的方法找到自变量的值,然后代入函数,就是最小值了。

#include<iostream>
#include<cmath>
using namespace std;
double dao(double x)
{
	return 42*pow(x,6) + 48*pow(x,5) + 21*pow(x,2) + 10*x;
}
double yuan(double x,double y)
{
	return 6*pow(x,7) + 8*pow(x,6) + 7*pow(x,3) + 5*pow(x,2) - y*x;
}
int main()
{
	int t;
	cin >> t;
	double l, r, m, min_, y, x;
	while(t--)
	{
		cin >> y;
		if(y <= 0)//递增 
		min_ = 0;
		else if(dao(100) -  y <= 0)
		min_ = yuan(100, y);
		else
		{
			l = 0, r = 100, m = 50;
			while(fabs(l - m) > 1e-5)
			{
				x = dao(m);
				if(x > y)//导数大于0 
				r = m;
				else
				l = m;
				m = (l + r)/2;
			}
			min_ = yuan(m,y);
		}
		printf("%.4f\n",min_);
	}
	return 0;
}

最后我们看一个比较复杂的问题:
我的生日要到了!根据习俗,我需要将一些派分给大家。我有 N 个不同口味、不同大小的派。有 F 个朋友会来参加我的派对,每个人会拿到一块派 (必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的 (但不必是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。

请问我们每个人拿到的派最大是多少?每个派都是一个高为 1,半径不等的圆柱体。
input
首先输入一个整数 T,表示测试数据的组数。

对于每组测试数据:
第 1 行包含两个正整数 N, F,分别表示派的数量和朋友的数量,满足 1 <= N, F <= 10000。
第 2 行包含 N 个 1 到 10000 之间的整数,表示每个派的半径。
output
每组测试数据对应一行,输出每个人能得到的最大的派的体积,误差不超过 10^(-3)。
sample input
3
3 3
4 3 3
1 24
5
10 5
1 4 2 3 4 5 6 5 4 2
sample output
25.1327
3.1416
50.2655
一看到这个问题,似乎很简单,但是如何代码实现却成了问题,我们可以用二分的思想,比如,每个人都分最大的能不能分下去,每个人都分最大的一半,能不能分下去,四分之一能不能分下去…
有了这个思路,我们就可以写出我们的代码了:

#include<iostream>
#include<algorithm>
using namespace std;
const double PI = 3.141592653589793;
double a[10010];
double  f, rr, r, m ,l;
int n;
bool dis(double x)
{
	int num = 0;
	for(int i = 1; i <= n; i++)//分派的数量能够达到人数要求 
	{
		num += (int)(a[i]/x);
	}
	if(num >= f)
	return true;
	else
	return false;
}
int main()
{
	int t;
	cin >> t;
	while(t--)
	{
		cin >> n >> f;
		f++;
		for(int i = 1; i <= n; i++)
		{
			cin >> rr;
			a[i] = rr * rr * PI * 100000;将数据扩大处理,以免丢失精度
		}
		sort(a+1,a+1+n);//排序
		r = a[n];
		l = 1;
		m = (r + l)/2;
		while(r > l + 1)二分
		{
			if(dis(m))
			l = m;
			else
			r = m;
			m = (l + r)/2;
		}
		printf("%.4f\n",m/100000);
	}
	return 0;
}

注:这题在oj上判的话,我们的π的小数位数要多一点,不然可能不不能AC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值