文章目录
前言
学习了几天的排序开始了二分的学习,二分的思想就是将一段数据不断从中间分割,在分出的两份数据中只留下需要的数据在的那一部分,对数据范围不断缩小,最后得到需要的数据。最常见的就是利用二分思想去解二元一次方程组,但是当方程组的次数增加的时候又该如何用二分的思想求解呢?
一、P1024 [NOIP2001 提高组] 一元三次方程求解
初步解题思路
1.最简单的思路无非是用暴力枚举,数据范围不是很大,运行不会超时
2.使用盛金公式直接进行求根
3.利用二分,对每个小区间进行求根
1.暴力枚举
#include<iostream>
#include<cmath>
#include<iomanip>
using namespace std;
int main()
{
double a, b, c, d;
cin >> a >> b >> c >> d;
for (double i = -100; i <= 100; i+=0.001)
{
double j = i + 0.001;
double y1 = a * pow(i, 3) + b * pow(i, 2) + c * i + d;
double y2 = a * pow(j, 3) + b * pow(j, 2) + c * j + d;
if (y1*y2<=0) {//直接判断根
cout << setiosflags(ios::fixed) << setprecision(2) << (i + j) / 2 << " ";
}
}
}
2. 盛金公式
这个没有什么技巧,全靠数学公式的运用
#include<iostream>
#include<cmath>
#include<iomanip>
using namespace std;
int main()
{
double a, b, c, d;
cin >> a >> b >> c >> d;
double A = pow(b, 2) - 3 * a * c;
double B = b * c - 9 * a * d;
double C = pow(c, 2) - 3 * a * d;
double Q = acos((2 * A * b - 3 * a * B) /( 2 * sqrt(pow(A, 3))));
double x1 = (-b - 2 * sqrt(A) * cos(Q / 3)) / (3 * a);
double x2 = (-b + sqrt(A) * (cos(Q / 3) - sqrt(3) * sin(Q / 3))) / (3 * a);
double x3 = (-b + sqrt(A) * (cos(Q / 3) + sqrt(3) * sin(Q / 3))) / (3 * a);
cout << fixed << setprecision(2) << x1 << " ";
cout << fixed << setprecision(2) << x2 << " ";
cout << fixed << setprecision(2) << x3 << " ";
}
3.小区间二分
#include<iostream>
#include<iomanip>
using namespace std;
double a, b, c, d;
double func(double x)
{
return a * x * x * x + b * x * x + c * x + d;
}
int main()
{
cin >> a >> b >> c >> d;
double l, r, m, x1, x2;
int s = 0;//留一个判断根个数的标记,用于减少时间
for (int i = -100; i < 100; i++)
{
l = i;
r = i + 1;
x1 = func(l);
x2 = func(r);
if (!x1)
{
cout << fixed << setprecision(2) << l << " ";
s++;
} //判断左端点,是零点直接输出;不能判断右端点,会重复。
if (x1 * x2 < 0) //区间内有根。
{
while (r - l >= 0.001) //二分控制精度。
{
m = (l + r) / 2; //计算中间值
if (func(m) * func(r) <= 0)//判断根所在的区间后只留下需要的区间,减小范围
l = m;
else
r = m;
}
cout << fixed << setprecision(2) << r << " ";
//输出右端点。
s++;
}
if (s == 3)
break;
//找到三个就退出,一元三次方程最多三个根
}
return 0;
}
二、P2249 【深基13.例1】查找
初步解题思路
仔细读题,已经告诉我们这里输入的所有的数都是递增的,所以不用排序直接进行查找就好了。利用二分的方法直接对每个数进行查找
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
int a[n];
for (int i = 0; i < n; i++)
cin >> a[i];
while (m--)
{
int s;
cin >> s;
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r>>1;//这里指取对l+r的一半而且右移一位,即向右取整
if (a[mid] >= s) r = mid;
else l = mid+1;
}
if (a[l] == s)cout << l + 1<<" ";
else cout << "-1 ";
}
return 0;
}
三.P1873 砍树
初步解题思路
先找到最高的树,取它的一半作为标准,取能砍的一棵树的木材求和,这样得到的木材的总和与需要的木材的长度进行比较。如果大了就将设定的最小值(0)改变为现在中间值大一点的值,最高值不改变;如果小了就将设定的最大值改变为现在中间值小一点,最小值不变。这样重复操作,直到最后取得想要的结果。
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
long long N, M,h=0;
cin >> N >> M;
long long a[N];
for (int i = 0; i < N; i++) {
cin >> a[i];
h = max(h, a[i]);
}
long long l = 0;
while (l <= h) {
long long mid = (l + h)/2;
long long sum = 0;
for (int i = 0; i < N; i++)
if(a[i]>mid)
sum += a[i] - mid;
if (sum < M)
h = mid-1;
else
l = mid + 1;
}
cout << l - 1;
return 0;
}
知识卡片
1.银行月利率计算方法
可以利用二分法进行逼近,具体实现过程看代码以及解释
double l = 0, r = 1000, mid = 0;
while (l < r - 0.0001) {//注意这里需要的精度
mid = (l + r) / 2;//缩短每一次的区间
double w = a;//a是原来的本金
for (int i = 0; i < c; i++)//c是还款时长
w = w - b + w * (mid / 100);//w是每个月的本金,mid/100是月利率
if (w > 0.0001)
r = mid;
else
l = mid;
}
cout<<l<<endl;//这里需要精度为多少就保留多少
总结
今天对二分有了一定的了解,初步做了几个题,其实思想很简单,但是要能够灵活运用还需要一些练习。二分也是能和其他很多算法只是综合起来出题的一种思想,要能够对能运用它的题目做出迅速的反应。