二分查找之答案
出自–南昌理工学院ACM集训队
什么是二分查找呢?
答:二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。(摘抄自百度)感觉什么都没说似的 其实不没有哦,必须有序,这一点很重要,可以是升序也可以是降序,但在这里一般是升序的,有序后就很好理解了。
(官方理解)首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
就我来说,话说怎么这么像英语 将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x(那么左边和右边其实就是左右边界)
适用于什么条件呢?
答:就我来说怎么感觉又来,好奇怪,好奇怪 ,第一,这个解不太好求出来,又不想放弃咋办?就像原来的数学题,不会写,那就猜答案,然后在验证答案是否正确。
第二,就是求最大值最小化or最小值最大化
二分查找怎么查找答案?
那查找出来的答案又应该怎么验证是否是正确的呢?
这些问题就是我们今天要讨论的问题了
上才艺(例题,例题,哈哈哈哈 )
P2678 跳石头
题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L , N , M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证L>=1且N>=M>=0 。
接下来 N 行,每行一个整数,第 i 行的整数 D_i(0< D_i< L),表示第 i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
一个整数,即最短跳跃距离的最大值。
输入输出样例
输入 #1
25 5 2
2
11
14
17
21
输出 #1
4
题目大意
此题要求最短跳跃距离的最大值,即最短距离最大,对于最短距离我们知道其范围是1~L,这是一个单调区间,就是1,2,3……L,那么可以在该单调区间内进行二分查找不断缩小范围。
那么还需要一个判断函数check来判断当前距离作为最短距离是否是可行解。如果是可行解,但有可能它不是最优解,那么因为求最大值我们还需要继续向其右部区间查找是否有更优解;如果不是可行解,那么可行解只可能在其左部区间,二分向左部查找。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
int l,n,m;
const int manx=3e5+7;
int a[maxn];
bool check(int x) //判断该x距离是否是一个合法解
{
int cnt=0; //cnt代表模拟跳石头的人当前在什么位置
int num=0; //代表计数器,记录以当前答案需要移走的实际石头数
for (int i=1;i<=n+1;i++) //n不是终点,n+1才是
{
if (a[i]-cnt<x) //如果该距离比最短距离还短,说明该石头需移走,计数器加1
num++;
else
cnt=a[i];
}
if (num>m) //需移走的石头总数超过了m,说明这个距离是非法解
return false;
return true;
}
int main()
{
int ans;
cin>>l>>n>>m;
for (int i=1;i<=n;i++)
cin>>a[i];
a[n+1]=l; //注意审题(n不是终点,右边界)
int left=1,right=l,mid;
while (left<=right) //基本的二分查找
{
mid=(left+right)/2;
if (check(mid)) //合法继续去右边找更大的,非法则去左边找合法的
{
ans=mid;
left=mid+1;
}
else
right=mid-1;
}
cout<<ans<<endl;
return 0;
}
一道题怎么够呢?再来一道吧
P1182 数列分段 Section II
题目描述
对于给定的一个长度为N的正整数数列 A _1∼N,现要将其分成 M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 4 2 4 5 1 要分成 33 段。
将其如下分段:
[4 2] [4 5] [1]
第一段和为 6,第 2 段和为 9,第 3 段和为 1,和最大值为 9。
将其如下分段:
[4][2 4][5 1]
第一段和为 4,第 2 段和为 6,第 3 段和为 6,和最大值为 6。
并且无论如何分段,最大值不会小于 6。
所以可以得到要将数列 4 2 4 5 1 要分成 3 段,每段和的最大值最小为 6。
输入格式
第 1 行包含两个正整数 N,M。
第 2 行包含 N 个空格隔开的非负整数 A_i ,含义如题目所述。
输出格式
一个正整数,即每段和最大值最小为多少。
说明/提示
对于 20%20% 的数据,N≤10。
对于 40%40% 的数据,N≤1000。
对于 100%100% 的数据,1≤N≤10 ^ 5 , M≤N,A_i < 10 ^ 8 , 答案不超过 10^9 。
解题思路:分析题目,求最大值的最小化,直接联想到二分。如果cnt大于了x,我们不加而是cnt重新赋值并且num++,最后只需判断num是否不小于m就行了。
注意: 二分时的区间取值问题,很明显,对于l的赋值应该取数列中的最大值,而r应该取数列的总和。。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e5+7;
int a[maxn];
int n,m;
bool check(int x)
{
int cnt=0, num=0;
for(int i=1;i<=n;i++) //如果和超过了最大值说明需要将其分段,分段数加1,重新开始计算cnt
{
if(cnt+a[i]<=x)
cnt+=a[i];
else
{
cnt=a[i];
num++;
}
}
if(num>=m)
return 1;
else
return 0;
}
int main()
{
int sum=0,l=0,r,mid;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
l=max(l,a[i]); //最小的和是最大的一个数,最大的和是所有数的总和
}
r=sum;
while(l<=r) //合法则向左找还有没有更优解,不合法则向右找合法解
{
mid=(l+r)/2;
if(check(mid))
l=mid+1;
else
r=mid-1;
}
cout<<l<<endl;
return 0;
}
总结一下
这类题目的核心是找到二分的范围区间进行二分查找,二分查找的条件通过一个check函数检测,check函数在不同题中有不同写法,但目的都是来判断一个解是否合法。
上个模板吧
const int N = 0x3f3f3f3f;
int main()
{
int ans, l, r;
int l = a, r = b;//a为上限,b为下限
while (l <= r)
{
mid = (l + r) / 2;
if (check(mid))
/*判断二分得到的mid是不是满足题目要求,
并且将二分的上限或下限相对应的缩小*/
{
ans = mid;
mid = l + 1;
}
else
mid = r - 1;
}
cout << ans << endl;
return 0;
}