二分算法模板

二分算法

活动地址:CSDN21天学习挑战赛


二分的本质并不是单调性,当一个数列满足单调性时一定可以二分,但二分不一定需要满足单调性。

并且二分一定有答案,没有答案那只是没有符合题目要求的答案。


二分查找算法模板

二分模板一共有两个,分别适用于不同情况。

算法思路:假设目标值在闭区间 [l, r]中, 每次将区间长度缩小一半,当 l = r时,我们就找到了目标值。

版本1

当我们将区间 [l, r]划分成 [l, mid][mid + 1, r]时,其更新操作是 r = mid或者 l = mid + 1;,计算 mid时不需要加 1。

C++代码模板:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}
版本2

当我们将区间 [l, r]划分成 [l, mid - 1][mid, r]时,其更新操作是 r = mid - 1或者 l = mid;,此时为了防止死循环,计算 mid时需要加 1。

C++代码模板:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

以上摘自 AcWing 中 y 总的分享:二分查找算法模板 - AcWing

简单来说就是:

可以将模板 1 中的 check[mid]换成a[mid] >= x,用来查找大于等于 x的第一个元素;
将模板 2 中的 check[mid]换成 a[mid] <= x,用来查找小于等于 x的最后一个元素。


一、二分查找

【例一】查找:

原题链接:P2249 【深基13.例1】查找 - 洛谷

题目描述:输入 n (n <= 106 ) 个不超过 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1, a2, …, an,然后进行 m (m <= 105 ) 次询问。对于每次询问,给出一个整数 q (q <= 109 ),要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 -1 。(数字从 1 开始编号)

代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;

int n, m;
int a[N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    while (m--)
    {
        int x;
        cin >> x;

        int l = 1, r = n;
        while (l < r) //找到第一个等于x的数
        {
            int mid = l + r >> 1;
            if (a[mid] >= x) r = mid;
            else l = mid + 1;
        }
		
        //此时l和r都指向同一个数,任意输出一个即可
        if (a[l] != x) cout << -1 << " ";
        else cout << l << " ";
    }
    cout << endl;

    return 0;
}
【例二】A-B 数对

原题链接:P1102 A-B 数对 - 洛谷

题目描述:给出一串数以及一个数字 C ,要求计算出所有 A - B = C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

这里使用库函数二分的写法:

依次枚举 A ,将问题转变成统计数列中 B + C 出现了多少次。先对数列排序,那么 B + C 会对应这个数列的连续一段,只要找到这个连续段的左端点和右端点即可。(需使用头文件 algorithm)

lower_bound(begin, end, val)可以在区间 [begin, end)中找到 val第一次出现的位置;

upper_bound(begin, end, val)可以在区间 [begin, end)中找到 val最后一次出现的位置的 后面一位

则这个数出现的次数就可以表示为 upper_bound() - lower_bound(),时间复杂度为 O(nlogn).

#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 2e5 + 10;

int n, c;
ll a[N];

int main()
{
    cin >> n >> c;
    for (int i = 0; i < n; i++) cin >> a[i];

    sort(a, a + n);

    ll tot = 0;
    for (int i = 0; i < n; i++)
        tot += upper_bound(a, a + n, a[i] + c) - lower_bound(a, a + n, a[i] + c);

    cout << tot << endl;

    return 0;
}

二、二分答案

一般来说,二分答案可以用来处理 “最大的最小” 或 “最小的最大” 的问题。

二分答案算法模板

定义区间为闭区间 [l, r],每次只需判断答案是否需要更新(是否记下ans)和(可能的)答案在哪一侧(改 L 还是 R )即可。

int ans;
int find(int l, int r)
{
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (check(mid)){
            ans = mid; //如果条件成立则记下答案
            r = mid - 1; //判断可能的答案更新区间
        }
        else l = mid + 1;
    }
    
    return ans;
}
【例题】进击的奶牛

原题链接:P1824 进击的奶牛 - 洛谷

题目描述:一个牛棚有 n 个隔间,它们分布在一条直线上,,坐标是 x1, x2, …, xn 。现在需要把 c 头牛安置在某些隔间,使得所有牛中相邻两头的最近距离越大越好,求这个 最大的最近距离

先构造判断 “条件”:可以把 c 头牛全部安置进这些隔间使相邻两头牛距离不超过 x 。x 越小,就越可能把所有牛合法安置;当 x 比较大时,牛棚就不够安置了。于是,存在一个分界线 ans,当 x 大于 ans 时就没有合法的安置方案,当 x 小于或等于 ans 时,则一定存在一个合法的安置方案。

可以得到,在合法的答案中,任意两个相邻安置点都不能小于 x 。那么只需要遍历所有点,从最左端开始,每隔超过 x 的距离,能安置则安置,最后判断能否全部安置完。若不能,则缩小 x ,重复上述遍历过程。

代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
const int INF = 1e9;

int n, c;
int a[N];

bool check(int d)
{
    int k = 0;
    int last = -INF; //记录上一头牛的安置坐标
    for (int i = 1; i <= n; i++)
        if (a[i] - last >= d) //能安置就立刻安置
        {
            last = a[i];
            k++;
        }

    return k >= c;
}

int main()
{
    cin >> n >> c;
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    sort(a + 1, a + 1 + n);

    int ans;
    int l = 0, r = INF;
    while (l <= r) //二分查找答案,最大的最小值
    {
        int mid = l + r >> 1;
        if (check(mid)){
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }

    cout << ans << endl;

    return 0;
}


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BraumAce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值