在我们的平常练习中,有时候被二分弄的找不到方向,甚至题解看起来不像二分,非常苦恼,看完这篇文章,将把二分学透;
二分 分为查找和答案,两个类型;
其实二分也是有模板的,下面来看一下二分的模板
模板一
while (l < r){
int mid = l + r >> 1; //这里相当于一个l+r/2,这样写是因为可以更好的节约空间;
if (check(mid)) r = mid; //这里的check是一个函数,用来判断;
else l = mid + 1;
}
模板二
while (l < r){
int mid = l + r + 1 >> 1; //这里的+1是为了防止死循环if (check(mid)) l = mid;
else r = mid - 1;
}
模板一是尽量向左边去查找,模板二是尽量向右边去查找
大多数的二分形式都是
int l = 1, r = n;
while (l <= r) {
int mid = l + r >> 1;
//m为要查找的数
if (a[mid] == m){
cout << mid;
break;
}
if (a[mid] < m)
r = mid - 1;
if(a[mid]>m)
l = mid + 1;
}
这种遇到复杂的问题,往往难以理解,所以建议用上面的模板解决,但是依然是需要去理解,不然只用模板,遇到问题也往往只有上文,没有下文
下面我们做几个题目去理解这两个模板
例题一:数组的二分查找之一
这是二分入门的查找问题,首先我们看是输出最小的那一个,就明白,应该向左去寻找,因为二分是在有单调性的序列中才能使用 ,又看见题目里面要求 ai>=x ,所以我们可以下出下面代码
在看题解之前可以自己先用模板写一下,这样效果更好
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int n, x, k;
int a[10000000];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> k;
while (k--) {
cin >> x;
int l = 1, r = n;//左右边界
while (l < r) { //模板一,往左边找
int mid = l + r >> 1;
if (a[mid] >= x) r = mid;//如果当前值大于等于目标,符合但是不是最小,继续往左边走
else l = mid+1;
}
if (a[l] != x&&a[l]<x) { //
cout << "no" << endl;//如果最后找的结果不符合,输出“no"
continue;
}
cout << l << endl;//上面条件不成立,到这里直接输出结果就行
}
return 0;
}
经过这个题目,你大概有些了解,相当于跨进了二分的大门了
下面进行一个变式,希望可以用上面的模板和思路解决
例题二 数组的二分查找2
还是先进行分析 ,这个题目唯一变的就是查找的条件变了,所以很简单,以下代码给出
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int n, x;
int a[10000000];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int k;
cin >> k;
while (k--) {
cin >> x;
int l = 1, r = n;
while (l < r) {
int mid = l + r >> 1;//和上一个题一样,往左边来,因为最小
if (a[mid] > x) r = mid;//这里的等于号不能用了,因为要求变了
else l = mid + 1;
}
if ( a[l] <= x) {
cout << "no" << endl;
continue;
}
cout << l << endl;
}
return 0;
}
相信这个题这是对上一个题的巩固和练习,不过相信你们还不不过瘾,所以。。。
例题三 数组的二分查找3
这里得看清楚题目,题目说的是最大的,所以应该往右边去查找
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int n, x;
int a[10000000];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int k;
cin >> k;
while (k--) {
cin >> x;
int l = 1, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;//往右边查找
if (a[mid] <= x) l = mid;//如果值小于等于目标值,说明在目标值左边,往右来,只有这样才能找到最大的那一个
else r = mid - 1;//相反就往左
}
if (a[l] != x) {//如果找不到这个值
cout << "no" << endl;
continue;
}
cout << l << endl;
}
return 0;
}
经过这几个题目的练习,相信大家对查找应该没什么大问题,剩下的就是巩固和练习
https://www.luogu.com.cn/problem/P1102
下面我们进入二分答案的世界,话不多说直接进入例题练习
例题一 砍树
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
using namespace std;
int a[10000005], n, m, maxa, sum;
int check(int mid) {
for (int i = 1; i <= n; i++) {
if (a[i] > mid) sum += a[i] - mid;
}
if (sum >= m)return 1;//如果sum>=m,说明砍多了,也就是锯子的高度太低了,所以我们要放高一点
else return 0;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] > maxa) maxa = a[i];
}
int l = 0, r = maxa;
while (l < r) {
long long mid = l + r + 1 >> 1;//因为我们砍太多了不行,砍少了也不行,也就是最大值的意思
sum = 0;//这里很容易忽略,每次都要清零
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}
经过这个题目就可以知道,其实二分答案与二分查找的区别就在于,二分答案在每次查找中都去比较运算一遍,而二分查找则是一直查找知道找出答案
例题二 爱独居的野猫
首先看题,他说什么?最大的最近距离,最近?模板一,尽量往左边去找,判断条件就是区间距离>=mid,这样才能使mid最小,也就是最近。
有了上一题的经验,我们可以马上敲出
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
using namespace std;
int n, c, a[100005], maxa;
int check(int mid) {
int cnt = 1;
int i = 1, now = 1;
while (i < n) { //这里的处理比较巧妙,也就是第一个和第二个,第二个和第三个依次比较,大家也可以用其他方法
i++;
if (a[i] - a[now] >= mid)//如果大于mid,才能保证mid是最小值,cnt++;
cnt++, now = i;
}
if (cnt < c) return 1;//如果cnt<c说明mid取大了,导致放完第二个猫以后,已经到了最后位置,无法放第三个猫
else return 0;
}
int main()
{
cin >> n >> c;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
int l = 0, r = a[n];
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;//因为mid取大了,所以我们得往左移动
else l = mid + 1;//取小了就往右边走
}
cout << l-1;//这里的-1是因为可能最后是l=mid+1,要-1才能得到正确答案(没有验证,仅个人理解)
return 0;
}
例题三:数列分段2
一样还是最大的值最小,下面就不给出分析了,这个题跟上一个题目大致相同
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
long long n, m, a[100005], maxa, mina = 1e9+1, sum;
int check(int mid) {
long long cnt = 1, ans = 0;//cnt之所以初始化为1,是你第一个数必成一段
for (int i = 1; i <= n-1; i++) {
ans += a[i];
if (ans + a[i + 1] >= mid) cnt++, ans = 0;//因为是最小值,所以要大于mid,
}
if (cnt <= m) return 1;//分段少了,也就是mid的值比较大,所以得小一点
return 0;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i], sum += a[i];
if (a[i] > maxa) maxa = a[i];
}
int l = maxa, r = sum;//注意这里的范围取值
while (l < r) {
int mid = l + r >> 1;
if (check(mid))r = mid;
else l = mid + 1;
}
cout << l-1;//上一个题目里面提到过
return 0;
}
有了上面的题目相信你对二分已经有了比较深的了解,其实二分答案都是大同小异,主要的就是check函数里面对题目要求的编写,这个地方要想清楚,下面给几个题目大家自己去完成,题目答案,大家在csdn也可以搜出
1.进击的奶牛
2.最佳牛围栏
这两个模板,还需要大家去多练习,每道题目都有自己的处理方式,这只是为了更加去理解和解决,关键还是在自己的处理上面,希望大家可以有收获
如果有不懂的地方可以留言,一起交流进步,我也是刚刚接触