ACM寒假集训

二分查找之答案


出自–南昌理工学院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;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值