2024年寒假算法班集训day01-知识总结及题解(二分)

二分查找算法通用模板

#include <iostream>
using namespace std;

int main() {
    int n, L = 0, R = 45, s = 0, mid;
    // 假设您要查找的范围是0-45,s用于记录步数
    cin >> n; // 输入要查找的数

    while (L <= R) {
        s++; // 每进行一次循环,步数加一
        mid = L + (R - L) / 2; // 计算中点

        if (mid == n) { // 如果中点就是要找的数
            break; // 找到数,结束循环
        } else if (mid > n) { // 如果中点大于要找的数
            R = mid - 1; // 将查找范围缩小到左半部分
        } else { // 如果中点小于要找的数
            L = mid + 1; // 将查找范围缩小到右半部分
        }
    }

    cout << s; // 输出查找所需的步数
    return 0;
}

这段代码的逻辑可以分为以下几个结构化的部分:

初始化变量:

  • int n: 存储用户输入的目标值。
  • int L = 0, R = 45: 定义搜索区间的左右边界,这里假设搜索范围为 0 到 45。
  • int s = 0: 记录循环的次数,即查找步骤数。
  • int mid: 用于存储每次计算的中点索引值。

输入目标值:

  • cin >> n;: 接收用户输入的目标值。

二分查找过程:

  • while (L <= R) {: 当左边界小于等于右边界时,执行循环。
    • mid = L + (R - L) / 2;: 计算中点索引值。
    • if (mid == n) {: 如果中点值等于目标值,结束查找。
    • else if (mid > n) {: 如果中点值大于目标值,移动右边界。
    • else {: 如果中点值小于目标值,移动左边界。
    • s++;: 每执行一次循环,步数增加一。

输出结果:

  • cout << s;: 查找结束后,输出总步数。

这个过程以一种迭代的方式逐渐缩小搜索范围,直到找到目标值或者搜索范围为空。

题解

1、字典找字-2828
在这里插入图片描述
题目要求通过编写代码,实现一个二分查找算法,并计算出找到指定数字 n 所需的步数。

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n,s=0,L=15,R=45,mid;
    cin>>n;
    while(L<=R){
    	s++;
        mid=(L+R)/2;
        if(mid==n){
        	break;
        }else if(mid>n){
        	R=mid-1;
        }else{
        	L=mid+1;
        }
    }
    cout<<s;
	return 0;
}

代码解析如下:

  1. 初始化变量 n(目标查找数字),s(步数),L(查找范围左界限),R(查找范围右界限),以及 mid(中间位置索引)。

  2. 使用 cin >> n; 语句从用户那里接收一个数字。

  3. 进入 while 循环,条件为 L 小于等于 R

  4. 在循环内部,首先将步数 s 加一。

  5. 计算中间位置 midLR 的平均值。

  6. 通过一系列 if-else 语句进行判断:

    • 如果 mid 等于 n,则找到了目标数字,使用 break 退出循环。
    • 如果 mid 大于 n,说明目标数字在左侧,更新 Rmid-1
    • 如果 mid 小于 n,说明目标数字在右侧,更新 Lmid+1
  7. 循环结束后,通过 cout << s; 输出寻找目标数字所需的步数。

  8. 最后,程序返回 0,表示正常结束。

2、猜数游戏2-2830
在这里插入图片描述
这道题目要求判断在一个1到10亿(1到1,000,000,000)的范围内,通过标准的二分查找算法,是否可以在20步以内找到给定的数字 n

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n,s=0,L=1,R=1000000000,mid;
    cin>>n;
    while(L<=R){
        s++;
        mid = (L+R)/2;
        if(mid==n){
            break;
        }else if(mid>n){
            R = mid-1;
        }else{
            L = mid+1;
        }
    }
    if(s<=20){
        cout<<"YES";
    }else{
        cout<<"NO";
    }
	return 0;
}

下面是对应代码的题解:

  1. 初始化变量 nL(左界限,初始值为1),R(右界限,初始值为10亿),s(步数,初始值为0),以及 mid(中间值)。

  2. 通过 cin>>n; 获取用户输入的数字 n

  3. 执行 while 循环,条件为 L <= R,即当左界限小于等于右界限时继续查找。

  4. 每执行一次循环,步数 s 加一。

  5. 计算中间值 midLR 的平均值 (L + R)/2

  6. mid 等于 n,使用 break 退出循环。

  7. mid 大于 n,更新右界限 Rmid - 1,缩小查找范围。

  8. mid 小于 n,更新左界限 Lmid + 1,缩小查找范围。

  9. 循环结束后,判断步数 s 是否小于等于20。如果是,输出"YES",表示可以在20步以内找到数字;如果不是,输出"NO"。

  10. 程序返回0,结束。

此代码片段演示了二分查找算法的效率,即在有序数据集中快速定位元素的能力。在最坏情况下,二分查找的复杂度为 O(log n),这意味着即使在高达10亿的范围内,也只需对数级别的步骤就能找到目标值。

3、团队猜数-2862
在这里插入图片描述
这段代码是针对一个特定的问题:在一个1到10亿的数值范围内,用户会输入3个数值,程序需要通过二分查找算法找到这些数值,并计算出总共需要多少步才能找到这些数值。

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n,s=0,L=1,R=1000000000,mid,s1=0;
    for(int i=1;i<=3;i++){
        cin>>n;
        while(L<=R){
            s++;
            mid = (L+R)/2;
            if(mid==n){
                break;
            }else if(mid>n){
                R = mid-1;
            }else{
                L = mid+1;
            }
        }
        s1 = s1+s;
        s = 0;
        L = 1;
        R = 1000000000;
    }
    cout<<s1;
  	return 0;
}

题解如下:

  1. 初始化变量:int n 用于存储用户输入的数值,int s 用于记录每一次查找的步数,int s1 用于累计所有查找步数,int Lint R 分别代表查找范围的起始和结束边界,int mid 用于存储中间值。

  2. 程序通过 for 循环让用户输入3次数值,每次数值输入后,都使用 while 循环来执行二分查找。

  3. 在二分查找的 while 循环中,每进行一次查找,s 就自增1。查找的逻辑是:

    • 如果 mid 等于 n,则跳出循环。
    • 如果 mid 大于 n,则将查找区间的右边界 R 更新为 mid - 1
    • 如果 mid 小于 n,则将查找区间的左边界 L 更新为 mid + 1
  4. 每完成一次查找,将此次查找的步数 s 加到 s1 上,然后重置 s 为0,并将 LR 重置为初始值。

  5. 查找完成后,输出 s1,它代表了找到3个数值所需的总步数。

  6. 程序最后返回0,表示正常结束。

4、查找一个数是否存在-3104
在这里插入图片描述
这道题要求在一个有序递增的数组中查找特定的值 x 的位置。如果找到 x,输出它在数组中的位置(基于1的索引);如果未找到,输出 -1

#include<iostream>
using namespace std;
int a[500000001];
int main(){
    int n,x;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    cin>>x;
    int L=1,R=n;
    while(L<=R){
        int mid = (L+R)/2;
        if(a[mid]==x){
            cout<<mid;
            return 0;
        }else if(a[mid]>x){
            R = mid-1;
        }else{
            L = mid+1;
        }
    }
    cout<<-1;
    return 0;
}

题解:

  1. 首先,程序分配了一个足够大的数组 a 来存储输入的数据。
  2. 程序接受输入 n 表示数组中的元素数量,然后接受 n 个递增的元素填充到数组 a 中。
  3. 输入 x 表示需要查找的元素。
  4. 通过二分查找算法来查找 x 的位置。在每次迭代中,计算中间索引 mid,比较 a[mid]x
  5. 如果 a[mid] 等于 x,输出 mid 并结束程序。
  6. 如果 a[mid] 大于 x,调整搜索范围为左侧的子数组;如果 a[mid] 小于 x,调整搜索范围为右侧的子数组。
  7. 如果查找结束后没有找到 x,输出 -1

注意:数组 a 使用了基于 1 的索引,这意味着第一个元素在 a[1] 而不是 a[0]。这与 C++ 的常规数组索引习惯(从0开始)不同。此外,数组的最大大小设置得非常大,以确保能够处理题目中可能的最大输入。在实际应用中,通常需要考虑内存限制来决定数组的最大大小。

5、造海船-1350
在这里插入图片描述
题目描述了一种木材切割问题,目标是最大化切割后小段木材的长度。给定n根原木和k段需要的木材,要求找到能切割出的最大长度l。

解题思路采用了二分查找的方法。通过不断调整l的值,寻找一个最大的l,使得所有原木切割后至少能得到k段木材。初始时,左边界l设为0,右边界r设为所有木材长度中的最大值。

代码流程如下:

  1. 输入原木的数量n和所需的木材段数k。
  2. 读入每根原木的长度,同时更新最大原木长度r。
  3. 使用二分查找确定最大的l。计算每根原木在长度为mid(当前的l和r的中点)时能切割出的段数ans。
  4. 如果切出的段数ans小于k,说明mid过大,需要缩小搜索范围,将r更新为mid。
  5. 如果ans大于或等于k,说明mid可能还不是最大值,将l更新为mid。
  6. 当l和r之间的差距为1时,结束搜索,此时的l即为所求的最大长度。

ps:想象一下,我们正在玩一个猜数字的游戏。我让你猜一个1到100之间的数,你每猜一次,我就告诉你猜大了还是猜小了。你可能会从50开始,如果我说大了,你下次会猜25,如果我还是说大了,你可能会猜12。这样你每次都减少一半的范围来猜。当你猜到只剩下两个数,比如说5和6,你知道如果5还是小了,那么答案就只能是6了。在这个二分查找的算法中,l
代表5,r 代表6,当它们只相差1时,你已经不能再分割区间了,这时l就是你最后能猜到的最大数,也就是这个问题中你能切出来的最长木材的长度。

最后输出l,即为能切割得到的最长的小段木材长度。如果l为0,则表示连1cm的小段都切不出来。

以下是C++代码示例:

#include<iostream>
using namespace std;
int a[100001];

int main() {
    int n, k, l = 0, r = 0;
    cin >> n >> k;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        r = max(r, a[i]);
    }
    while (l + 1 < r) {
        int mid = (l + r) / 2;
        long long ans = 0;
        for (int j = 0; j < n; j++) {
            ans += a[j] / mid;
        }
        if (ans < k) {
            r = mid;
        } else {
            l = mid;
        }
    }
    cout << l;
    return 0;
}

注意:在比较过程中,ans被定义为 long long 类型,以避免在累加过程中发生整数溢出的情况。

6、给定的和-1787
在这里插入图片描述
题目要求找出给定整数数组中是否存在两个数的和为指定的值 m。如果存在,输出这两个数,小的数在前,如果有多对数的和为 m,输出较小数最小的那对。如果不存在这样的数对,输出"No"。

解题思路是先对数组进行排序,然后用二分查找的方法寻找是否存在和为 m 的两个数。对于数组中的每个数 a[i],在 a[i] 后面的区间内二分搜索一个数 a[mid],使得 a[i] + a[mid] 的和等于 m

代码流程如下:

  1. 输入整数个数 nn 个整数。
  2. 输入目标和 m
  3. 对数组 a 进行排序。
  4. 对数组中的每个元素 a[i],设置查找区间的左边界 li+1,右边界 rn-1
  5. [l, r] 区间内二分查找一个数 a[mid] 使得 a[i] + a[mid] 等于 m
  6. 如果找到满足条件的数对,则输出,并结束程序。
  7. 如果对于所有 i 都没有找到满足条件的数对,则输出"No"。
#include<bits/stdc++.h>
using namespace std;
int a[100001];
int main(){
    int n,m;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    cin>>m;
    sort(a,a+n);
    for(int i=0;i<n;i++){
        int l=i+1,r=n-1;
        while(l<=r){
            int mid=(l+r)/2;
            if(m==a[i]+a[mid]){
                cout<<a[i]<<" "<<a[mid];
                return 0;
            }else if(m<a[i]+a[mid]){
                r=mid-1;
            }else{
                l=mid+1;
            }
        }
    }
    cout<<"No";
    return 0;
}

这段代码利用了二分查找的高效性来降低问题的时间复杂度,从而可以在较大数据集上快速找到结果。

7、程程的生活费-1786
在这里插入图片描述
题目描述了一个预算规划问题。给定 N 天的日常花费,程程需要规划 M 个连续的财务周期(pr 月),目标是让最昂贵的 pr 月的花费尽可能低。

想象一下,程程为了过节日,买了一堆糖果要装进他的几个口袋里。每个口袋代表一个财务周期(pr月),而糖果的数量代表每天的花费。程程希望所有口袋里最满的那个口袋里的糖果尽可能少,这样他就不会在任何一个周期里花太多钱。
例如,程程有7块糖果,分别标价100、400、300、100、500、101、400元,他想把这些糖果分进5个口袋里。他怎么分配呢?如果他把前两块糖果作为一个口袋,第三、四块糖果作为第二个口袋,最后三天每块糖果各占一个口袋,那最满的口袋里就是500元的那块糖果。如果他用其他方式分配,可能会有一个口袋里的糖果总价更高。
他用一种猜数字的游戏来找出这个最合适的糖果分配方式。如果猜的太小,他的口袋不够;猜的太大,糖果可能装不满。所以他试着猜中间的数,直到找到一个刚好让所有口袋均匀分配糖果的最佳数值。这就是二分查找算法在这个问题上的应用。

解题思路使用了二分查找来确定“最大月度花费的最小值”。算法的核心是函数 check,它计算给定上限 x 下,所需的财务周期数,如果这个数小于或等于 M,那么这个上限是可行的。

代码流程如下:

  1. 读入 N 和 M 的值,以及接下来 N 天的花费。
  2. 初始化二分查找的左右边界:最大单日花费 max1 为左边界,总花费 s 为右边界。
  3. 通过二分查找确定最佳的花费上限 ans
  4. 在每次循环中,计算中间值 mid,并使用 check 函数检查 mid 是否为一个可行的上限。
  5. 如果 check(mid) 返回 true,说明可以有一个更低的上限,更新答案 ansmid,并调整右边界。
  6. 如果 check(mid) 返回 false,说明上限太低,需要调整左边界。
  7. 最后输出 ans,即满足条件的最大月度花费的最小值。
#include<bits/stdc++.h>
using namespace std;
int a[100001];
int n,m;
bool check(int x){
    int sum=0,yfen=1;
    for(int i=1;i<=n;i++){
        if(sum+a[i]>x){
            sum=a[i];
            yfen++;
        }else{
            sum+=a[i];
        }
    }
    return yfen<=m;
}
int main(){
    cin>>n>>m;
    int s=0,max1=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        max1=max1<a[i]?a[i]:max1;
        s+=a[i];
    }
    int l=max1,r=s,ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    cout<<ans;
    return 0;
}

这个问题是一个典型的二分搜索应用,通过这种方法可以有效地在大范围内找到最优解。

8、跳石头比赛-1790
在这里插入图片描述
题目是关于最优化奶牛跳石头的距离。给定一个河道和一系列岩石的位置,农夫约翰希望通过移除至多M块岩石,使得奶牛在河中跳跃的最短距离尽可能地长。

代码的解决思路采用二分查找,确定最短跳跃距离的最大可能值。以下是题解:

  1. 输入总距离S,岩石数N,以及可以移除的岩石数M。
  2. 输入每块岩石的位置。
  3. 设置二分查找的初始左边界l为1(最小可能跳跃距离),右边界r为L(河道的总长度)。
  4. 通过函数f来检查中间值mid(假定的最短跳跃距离)是否可行,即在移除至多M块岩石的情况下,能否保证所有跳跃的距离都大于等于mid。
  5. 如果f返回true,则说明mid是一个合理的最短跳跃距离,应尝试更大的值,将左边界l调整为mid+1。
  6. 如果f返回false,则说明mid过大,应尝试更小的值,将右边界r调整为mid-1。
  7. 循环结束时,ans将保存最短跳跃距离的最大可能值,输出ans。

函数f的工作原理是,从起点开始,对于每块岩石,如果当前岩石与上一次跳跃点的距离小于mid,则需要移除这块岩石(计数器cnt加1),否则将这块岩石作为新的跳跃点。最后检查从最后一个岩石到终点的距离是否也满足最短跳跃距离大于等于mid。如果移除的岩石数量超过M,则mid不可行。

#include<bits/stdc++.h>
using namespace std;
int L,n,m;
int a[500001];
bool f(int mid){
    int d=0,cnt=0;
    for(int i=1;i<=n;i++){
        if(a[i]-d<mid){
            cnt++;
        }else{
            d=a[i];
        }
    }
    if(L-d<mid){
        cnt++;
    }
    return cnt<=m;
}
int main(){
    cin>>L>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    int l=1,r=L,ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(f(mid)){
            ans=mid;
            l=mid+1;
        }else{
            r=mid-1;
        }
    }
    cout<<ans;
    return 0;
}

代码通过这种方式找到了能保证所有跳跃距离都尽可能长的最短跳跃距离的最大值。

9、饲养斗牛-1483
在这里插入图片描述

这个问题是关于如何在一条直线上分配牛舍的位置,以最大化最近两头牛之间的距离。解题思路采用二分查找方法来寻找这个最大的最小距离。

代码流程如下:

  1. 输入牛舍的数量 n 和牛的数量 m
  2. 输入每个牛舍的位置,并对位置进行排序。
  3. 设置二分查找的初始左边界 l 为1(最小可能距离),右边界 r 为最远牛舍的位置。
  4. 通过函数 check 来检查中间值 mid(假定的最小距离)是否可行,即在该距离下能否放置至少 m 头牛。
  5. 如果 check(mid) 返回 true,则说明 mid 是一个合理的最小距离,应尝试更大的值,将左边界 l 调整为 mid + 1
  6. 如果 check(mid) 返回 false,则说明 mid 过小,应尝试更小的值,将右边界 r 调整为 mid - 1
  7. 最后输出 ans,即满足条件的最大最小距离。

函数 check 的工作原理是,从第一头牛开始,对于每头牛,检查是否能在当前 mid 距离下放置下一头牛。如果可以,就更新当前牛的位置,并增加计数器。如果计数器达到或超过 m,表示可以在该距离下放置所有牛。

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100001];
bool check(int mid){
    int cnt=1;
    int now=a[1];
    for(int i=2;i<=n;i++){
        if(a[i]-now>=mid){
            cnt++;
            now=a[i];
        }
    }
    return cnt>=m;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+n+1);
    int l=1,r=a[n],ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)){
            ans=mid;
            l=mid+1;
        }else{
            r=mid-1;
        }
    }
    cout<<ans<<endl;
    return 0;
}

代码通过这种方式找到了能保证牛舍之间最小距离最大化的最优解。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值