理解:
(1)二分的本质不是单调性,它和单调性的关系是这样的:如果有单调性,一定可以二分,但可以二分的题目,一定要有单调性;
(2)二分可以解决:所有有调用的,和部分没单调性的;
(3)二分查找的判定树一定是平衡二叉树,但平衡二叉树不一定是二分查找的;
(4)二分的本质是边界:只要找到某种性质,使得整个区间一分为二,那么就可以用二分把边界 点二分出来,二分既可寻找绿色边界,又可寻找红色边界;
(5)二分不要局限于单调性;
整数二分:
1、理解:
红向上取:
如果mid = l + r , l = 2,r = 3, mid = (2 + 3) / 2 = 2 ,l = mid = 2;
更新完后的区间和之前一样,死循环。
绿向下取
2、代码
#include<bits/stdc++.h>
using namespace std;
bool check(int x)//检查x是否满足某种性质
{
......
}
//找位置尽可能靠左的板子
int bsearch_1(int l,int r)
{
while(l < r)
{
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
//找位置尽可能靠右的板子
int bsearch_2(int l, int r)
{
while(l < r)
{
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
浮点数:
#include<bits/stdc++.h>
using namespace std;
bool check(double x) // 检查x是否满足某种性质
{
.......
}
double bsearch(double l,double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while(r - l > eps)
{
double mid = (l + r) / 2;
if(check(mid) == 1)
{
r = mid;
}
else
{
l = mid;
}
}
return l;
}
题目:
题目1:(整数二分裸题)
给定一个按照升序排列的长度为 n 的整数数组,以及 q个查询。
对于每个查询,返回一个元素 k的起始位置和终止位置(位置从 0开始计数)。
如果数组中不存在该元素,则返回 -1 -1
。
输入格式
第一行包含整数 n和 q,表示数组长度和询问个数。
第二行包含 n个整数(均在 1∼10000范围内),表示完整数组。
接下来 q行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1
。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int main()
{
int n,m;
cin >> n >> m;
for(int i = 0 ; i < n ; i ++) cin >> a[i];
while(m --)
{
int x;
cin >> x;
//寻找左边界
int l = 0,r = n - 1;
while(l < r)
{
int mid = l + r >> 1;
if(a[mid] == x) r = mid; //
else if(a[mid] > x) r = mid;
else l = mid + 1;
}
//除了循环,l和r相等,区间只有一个数即我们二分出来的边界
if(a[l] != x) cout << "-1 -1" << endl; // 左边界是第一次出现的数,如果不等于x的话,就证明序列中没有这个数
else
{
cout << l << ' ';
int l = 0, r = n - 1;
//寻找右边界
while(l < r)
{
int mid = l + r + 1 >> 1;
if(a[mid] == x) l = mid;
else if(a[mid] < x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
题目2:(整数二分裸题)
题目描述:
统计一个数字在排序数组中出现的次数。
例如输入排序数组 [1,2,3,3,3,3,4,5]和数字 3,由于 3 在这个数组中出现了 4 次,因此输出 4
。
数据范围
数组长度 [0,1000]。
样例
输入:[1, 2, 3, 3, 3, 3, 4, 5] , 3
输出:4
核心代码:
int l = 0, r = nums.size() - 1;
while( l < r)
{
int mid = l + r >> 1;
if(nums[mid] < k) l = mid + 1;
else r = mid;
}
if(nums[l] != k) return 0;
int left = l;
l = 0,r = nums.size() - 1;
while(l < r)
{
int mid = l + r;
if(nums[mid] <= k) l = mid;
else r = mid - 1;
}
return r - left + 1;
题目3:(实数二分裸题)
题目描述:
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6位小数。
数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int main()
{
double x;
cin >> x;
double l = -100, r = 100;
while(r - l > 1e-8)
{
double mid = (l + r) / 2;
if(mid * mid * mid >= x) r = mid;
else l = mid; // 浮点数二分不需要加1减1,非常舒服
}
printf("%.6lf\n",l);
return 0;
}
题目4:
题目描述:
农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于 1 头,也不会超过 2000 头。约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。
输入格式:
第一行输入整数 N 和 F,数据间用空格隔开。
接下来 N 行,每行输入一个整数,第 i+1 行输入的整数代表第 i 片区域内包含的牛的数目。
输出格式:
输出一个整数,表示平均值的最大值乘以 1000 再向下取整之后得到的结果。
数据范围:
1≤N≤100000
1≤F≤N
思路:
这里要我们求连续不小于f块地的栅栏所围的奶牛的平均数的最大值。
根据要求可知,所求平均数最大值一定在每单个区域奶牛数量的最大值和最小值之间。
那么现在就可以很明显的看出,这是一个二分查找左区间的右边界点的题目。
我们只需从每单个区域奶牛数量的最大值和最小值之间二分查找答案即可。
AC代码:
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
double a[N], sum[N];
double l, r;
int n, f;
bool check(double x)
{
int i,j;
double min_lim = 0;
for (i = 1; i <= n; i++) sum[i]= sum[i - 1] + a[i]- x;
for (i = 0, j = f; j <= n; j ++, i ++)
{
min_lim = min(min_lim, sum[i]);
if (sum[j] - min_lim >= 0) return 1;
}
return 0;
}
double bsearch(double l,double r)
{
while (r - l > 1e-5)
{
double mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid;
}
return r;
}
int main()
{
int i;
cin >> n >> f;
l = 0; r = 0;
for (i = 1; i <= n; i ++)
{
cin >> a[i];
l = min(l, a[i]);
r = max(r, a[i]);
}
r = bsearch(l,r);
cout << int(1000 * r) << endl;
return 0;
}