二分算法学习笔记与总结

二分算法学习笔记与总结

二分

二分查找 侧重于查找一个元素是否存在,而 二分答案 则侧重于找到答案。

二分原理

二分,分而为二。
二分算法,顾名思义,就是把一组有序数据的搜索区域缩小一半。

整数二分

二分查找原理

一种查找方式

将搜索区域按是否满足条件 check() 缩小一半
根据情况可能存在两种情况

对于区间 [l,r]mid 为中点:

  1. [l,mid][mid+1,r] ,此时 mid=l+r2
  2. [l,mid1][mid,r] ,此时 mid=l+r2

注意: 情况1为下取整,情况2为上取整。

情况 1 下取整: C++ 自动下取整
情况 2 上取整: 当 l=r1 时, mid=l+r2=l,此时左区间为 [l,l1]。下一次迭代时会出现问题 l>r,将导致程序故障或 死循环

二分查找模板

AcWing 模板入口

模板一

  1. [l,mid][mid+1,r] ,此时 mid=l+r2
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1; // +1
    }
    return l;
}

模板二

  1. [l,mid1][mid,r] ,此时 mid=l+r2
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1; // +1
        if (check(mid)) l = mid;
        else r = mid - 1; // -1
    }
    return l;
}

-1+1
二分结束后,左端点将等于右端点,即 l == r

分析题目时,从 check() 入手,再判断使用哪一个模板

二分查找一定有解(一定可求出边界)

二分查找用法

本质并非单调性
有单调性就可以二分
二分不一定非得有单调性
二分的本质:寻找边界——满足条件的边界(左右)

题目1 (模板)(二分查找)

AcWing 789. 数的范围 题目入口

题目大意

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 $0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1

题目分析

这是一道经典的模板题

包含两个模板,需要两次二分。
第一次找出第一个小于等于 k 的数,第二次找出大于等于 k 的数。

第一次二分查找过程(图片中 x 表示 k
第一次二分查找过程
第二次同理

当第一次二分结束后,如果 lr 指向的数不等于 k,则表明此数组中不存在 k

点击查看二分模板

模板一

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1; // +1
    }
    return l;
}

模板二

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1; // +1
        if (check(mid)) l = mid;
        else r = mid - 1; // -1
    }
    return l;
}

CODE

Code1 手打模板
#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, T;
int q[N];

int main()
{
    scanf("%d%d", &n, &T);
    for (int i = 0; i < n; i ++ )
        scanf("%d", &q[i]);

    while (T -- )
    {
        int x;
        scanf("%d", &x);

        int l = 0, r = n - 1; // 模板一
        while (l < r)
        {
            int mid = l + r >> 1; // (l + r) / 2
            if (q[mid] >= x) r = mid; // 找出第一个 <=x 的数
            else l = mid + 1;
        }
        /* 此时 l = r */
        if (q[l] != x) // 说明数组中不存在 x
        {
            puts("-1 -1");
            continue;
        }
        else printf("%d ", l);

        l = 0, r = n -1; // 模板二
        while (l < r)
        {
            int mid = l + r + 1 >> 1; // 有 r = mid - 1,所以需要上取整
            if (q[mid] <= x) l = mid; // 找出第一个 >=x 的数
            else r = mid - 1;
        }
        printf("%d\n", l);
    }

    return 0;
}
Code2 STL 实现
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n, T;
int q[N];

int main()
{
    scanf("%d%d", &n, &T);
    for (int i = 0; i < n; i ++ )
        scanf("%d", &q[i]);

    while (T -- )
    {
        int x;
        scanf("%d", &x);
        
        int _l = lower_bound(q, q + n, x) - q; // 第一个大于等于x的数的下标 
        
        if (q[_l] != x) // 不存在x
        {
            puts("-1 -1");
            continue;
        }
        
        int _r = upper_bound(q, q + n, x) - q; // 第一个大于x的数的下标 
        
        printf("%d %d\n", _l, _r - 1);
    }

    return 0;
}

题目2 (运用)(二分查找)

洛谷 P1102 A-B 数对 题目入口

题目大意

给出一串有 n 个正整数的正整数数列以及一个正整数 C,要求计算出所有满足 AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

题目分析

AB=C 转为 A+C=B.
所以只需要从 1n 枚举 A

CODE

Code1 手打模板
#include <bits/stdc++.h>

#define int long long // 本题范围可能爆longlong 

using namespace std;

const int N = 2e5 + 10;

int n, c;
int a[N];
int ans;

signed main()
{
	cin >> n >> c;
	for (int i = 1; i <= n; i ++ )
		cin >> a[i];
	sort(a + 1, a + n + 1); // 使数组具有单调性
	for (int i = 1; i <= n; i ++ )
	{
		int x = a[i] + c;
		int l = 1, r = n;
		while (l < r) // 模板一 
		{
			int mid = l + r >> 1;
			if (a[mid] >= x) r = mid;
			else l = mid + 1;
		}
		if (a[l] != x) continue; // 不存在x 
		int _r = l; // 记录左端点 
		l = 1, r = n;
		while (l < r) // 模板二 
		{
			int mid = l + r + 1>> 1;
			if (a[mid] <= x) l = mid;
			else r = mid - 1;
		}
		ans += l - _r + 1;
	}
	cout << ans << endl;
	return 0;
}
Code2 STL 实现
#include <bits/stdc++.h>

#define int long long // 本题范围可能爆longlong 

using namespace std;

const int N = 2e5 + 10;

int n, c;
int a[N];
int ans;

signed main()
{
	cin >> n >> c;
	for (int i = 1; i <= n; i ++ )
		cin >> a[i];
	sort(a + 1, a + n + 1); // 单调性
	for (int i = 1; i <= n; i ++ )
		ans += (upper_bound(a + 1, a + n + 1, a[i] + c) - a) - (lower_bound(a + 1, a + n + 1, a[i] + c) - a);
	cout << ans << endl;
	return 0;
}

STL 中的二分查找

最详讲解
lower_bound, upper_bound, greater, less 用法

lower_bound()upper_bound 这两个函数是 STL 中用于二分查找的两个函数。

包含于头文件 algorithm,即 #include<algorithm>

lower_bound()

对于 lower_bound(begin, end, key) 返回值是一个 迭代器 在区间 [begin,end) 返回指向大于等于 key 的第一个值的 位置
其中,beginend地址keylower_bound() 的返回值是 地址

对于有一个有序的数组 a,并将数 x 作为二分查找的目标。

lower_bound(a, a + n, x) - a;           //下标从0开始
lower_bound(a + 1, a + n + 1, x) - a;   //下标从1开始

它们就能取得 最小a 数组的下标 i,满足 aix
lower_bound() 返回值是地址,减去数组名(数组名同时是指向数组头的指针)即为下标

upper_bound()

upper_bound()lower_bound() 用法一致。

对于有一个有序的数组 a,并将数 x 作为二分查找的目标。

upper_bound(a, a + n, x) - a;           //下标从0开始
upper_bound(a + 1, a + n + 1, x) - a;   //下标从1开始

它们就能取得 最小a 数组的下标 i,满足 ai>x

浮点二分

浮点数二分模板

bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

注意: 因为是浮点数,所以不需要 +1-1, 不能使用 >> 1

浮点数二分答案模板题目

AcWing 790. 数的三次方根 题目入口

更多题目

https://www.luogu.com.cn/training/111

posted @   Mingrui_Yang  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示