第三章 二分运算

二分算法,作为一种经典的算法策略,其核心精髓在于“二分”二字。顾名思义,它类似于数学中的二等分操作,即在算法中不断将待搜索的范围一分为二,以此高效缩小搜索空间,直至精确定位到目标元素或确认其不存在。。

1 何为二分?

二分算法是一种专为有序数据集(如数组或列表)设计的快速搜索算法。其核心思想在于,通过不断将搜索区间划分为更小的两个部分,并根据中间元素与目标值的比较结果,智能地选择保留包含目标值的那部分区间,从而逐步缩小搜索范围,直至找到目标元素的确切位置或确认其不存在于当前数据集中。

1.1 模拟二分过程:

想象一下,在一条从1到9的数轴上,我们急于找到某个特定目标点A(比如数字4)。为了高效地完成这一任务,我们可以采用二分搜索策略:

首先,我们确定搜索的初始范围,即数轴上的1至9区间。

然后,我们计算该区间的中点(mid=5),作为我们的初步参考点

 

接下来,我们比较中点与目标点A的大小。发现mid(即5)大于A(即4),这意味着目标点A位于中点的左侧。因此,我们果断地将搜索范围调整为左半部分,即从1到5。

 

在新的搜索范围内(1—5),我们再次执行上述过程,计算中点并比较。这次,我们会根据比较结果继续缩小搜索范围,直至找到目标点A的确切位置。

值得注意的是,在调整搜索范围时,我们总是移动那些使目标点可能落在搜索范围之外的端点(第一次我们移动右端点R,第二次移动左端点L)。这是因为,我们的目标是保持搜索范围始终包含目标点(如果它存在的话),而移动可能排除目标点的端点则能有效缩小搜索空间 。

2 整数二分

2.1 例题

我们从一个例题入手来分析整数二分。


蓝桥 【3344】 可凑成的最大花束数

问题描述

情人节到了,妮妮学姐的追求者实在太多了,她一共有 n 个追求者,第 i 个追求者赠送了 ai​ 朵颜色相同的花朵。每个追求者赠送的花朵颜色都不同。为了卖掉这些花并将所得款项捐赠给希望小学,妮妮学姐决定将 k 朵颜色不同的花朵打包成一个花束。请问她最多可以打包成多少个花束?

输入格式

第一行输入两个整数 n 和 k,分别表示妮妮学姐的追求者数量和打包需要的花朵数。

第二行输入 n 个整数 ai​,表示每个追求者赠送的花朵数量。

数据范围保证:1≤𝑛,𝑘≤2×10^{5},1≤ai​≤1\times 10^{9}

输出格式

输出一个整数表示妮妮学姐最多可以打包成多少个花束。

样例输入

2 2
5 6

样例输出

5

 2.2  分析

我们考虑总共需要的花朵数,假设答案为 x ,那么我们需要的花朵是则为 x×k 朵。对于第 i 位追求者送的花我们思考他能为答案提供多少有效的花朵,显然是 min(ai​,x) 。这样我们就可以统计出每个人可提供的有效花朵的和是否达到了x×k,如果达到则说明我们可以凑出x 束花束,否则则不行。

2.3 代码

#include <iostream>
using namespace std;
const int N = 1e6 + 2;
#define ll long long
ll a[N];
int n, k;

bool cmp(ll mid)
{
    ll ans = 0;
    for (int i = 0; i < n; i++)
    {
        ans += (min(mid, a[i]));
    }
    return (ans / k) >= mid;
}

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

 我们来逐一分析一下代码

    ll l = 0, r = 1e15;
    while (r > l)
    {
        ll mid = (l + r + 1) / 2;
        if (cmp(mid))
            l = mid;
        else
            r = mid - 1;
    }

这里我们先设定了初始搜索范围(0—10^{15}),在右端点大于左端点的前提下,每次找到区间的中点mid,判断是否符合条件来移动左右区间,最后左右端点重合即找到最大值。

为什么要写成 mid = (l + r + 1) / 2 和 r = mid - 1

本质是整数除法时避免下取整带来的问题。

1. mid = (l + r + 1) / 2
  • 目的:当处理整数除法时,为了避免在某些情况下丢失中间值,确保mid更偏向右侧。这在搜索区间为[l, r]且需要找到第一个满足条件的mid时特别有用。
  • 解释:如果lr都是整数,那么(l + r) / 2的结果将向下取整。如果lr的差是奇数,这个操作将丢失中间的整数值。通过加1,你可以确保在lr差为奇数时,mid会偏向r,从而可能在循环中更快地找到满足条件的解或确保最终lr相遇在正确的点上。
2. r = mid - 1
  • 目的:缩小搜索范围,排除mid值。
  • 解释:在二分查找中,当你发现mid不满足条件时(在这个例子中,cmp(mid)返回false),你需要更新搜索范围以排除mid。将r设置为mid - 1可以确保下一次循环中不会再次选择mid作为中点。

什么时候加一,什么时候减一呢?

 


 

bool cmp(ll mid)
{
    ll ans = 0;
    for (int i = 0; i < n; i++)
    {
        ans += (min(mid, a[i]));
    }
    return (ans / k) >= mid;
}

 判断能不能打包成花束。

3 浮点数二分

浮点数二分与整数二分类似,只是浮点数二分不需要考虑下取整带来的加一减一的问题。

3.1 模板

const double eps = 1e-9; // 定义精度误差范围  
  
bool cmp(double mid) {  
    // 判断条件  
    // 注意:这里需要根据题目要求来编写cmp函数  
    // 可能需要考虑浮点数的精度问题  
}  
  
int main() {  
    double l = 0, r = 1e9 + 3;  
    while (r - l > eps) {  
        double mid = (l + r) / 2.0;  
        if (cmp(mid)) {  
            l = mid;  
        } else {  
            r = mid;  
        }  
    }  
    // 由于浮点数的精度问题,最终l和r可能会非常接近但不相等  
    // 可以选择输出l、r或它们的平均值,具体取决于题目要求   
    return 0;  
}

3.2 注意事项

  1. 循环条件:通常使用 while (r - l > eps) 作为循环条件,其中 eps 是一个很小的正数,表示精度误差范围。
  2. 计算中点:直接使用 (l + r) / 2.0(注意使用 2.0 来确保进行浮点数除法)。
  3. 更新搜索范围:根据 cmp(mid) 的返回值来更新 l 或 r
  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值