首页 > 编程语言 >二分算法学习笔记与总结

二分算法学习笔记与总结

时间:2023-07-02 17:12:22浏览次数:67  
标签:二分 int mid 笔记 bound 算法 查找 模板

二分算法学习笔记与总结

目录

二分

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

二分原理

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

整数二分

二分查找原理

一种查找方式

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

对于区间 \(\left[ l, r \right]\) , \(mid\) 为中点:

  1. \(\left[ l, mid \right]\) 与 \(\left[ mid + 1, r \right]\) ,此时 \(mid = \lfloor \cfrac{l + r}{2} \rfloor\)
  2. \(\left[ l, mid - 1 \right]\) 与 \(\left[ mid, r \right]\) ,此时 \(mid = \lceil \cfrac{l + r}{2} \rceil\)

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

情况 1 下取整: C++ 自动下取整
情况 2 上取整: 当 \(l=r-1\) 时, \(mid=\lfloor \cfrac{l + r}{2} \rfloor = l\),此时左区间为 \(\left[ l, l - 1 \right]\)。下一次迭代时会出现问题 \(l > r\),将导致程序故障或 死循环

二分查找模板

AcWing 模板入口

模板一

  1. \(\left[ l, mid \right]\) 与 \(\left[ mid + 1, r \right]\) ,此时 \(mid = \lfloor \cfrac{l + r}{2} \rfloor\)
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. \(\left[ l, mid - 1 \right]\) 与 \(\left[ mid, r \right]\) ,此时 \(mid = \lceil \cfrac{l + r}{2} \rceil\)
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\),要求计算出所有满足 \(A - B = C\) 的数对的个数(不同位置的数字一样的数对算不同的数对)。

题目分析

将 \(A - B = C\) 转为 \(A + C = B\).
所以只需要从 \(1\) 至 \(n\) 枚举 \(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 这两个函数是 STL 中用于二分查找的两个函数。

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

lower_bound()

对于 lower_bound(begin, end, key) 返回值是一个 迭代器 在区间 \(\left [ begin, end\right )\) 返回指向大于等于 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\),满足 \(a_i \geq x\)。
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\),满足 \(a_i>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;
}

浮点数二分答案模板题目

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

更多题目

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

标签:二分,int,mid,笔记,bound,算法,查找,模板
From: https://www.cnblogs.com/MingruiYang/p/17489023.html

相关文章

  • 「模版」二分查找(lower_bound )
    七彩评测题目描述给出有n个元素的由小到大的序列,请你编程找出某元素第一次出现的位置。(n<=1000000)Input第一行:一个整数,表示由小到大序列元素个数:下边n行,每行一个整数:最后一行一个整数x,表示待查找的元素。Output如果x在序列中,则输出x第一次出现的位置,否则输出-1.......
  • C语言笔记:第8章 字符输入输出
    字符函数getchar()、putchar()与EOF详解:https://www.cnblogs.com/52php/p/5723666.html缓冲区:https://www.cnblogs.com/xkdn/p/14580178.htmlhttps://www.cnblogs.com/buyizhiyou/p/5505280.html ......
  • Snap算法学习01-03Snap中的类及其定义
        //graph.h定义的基本类型无向图  ///Undirectedgraph.##TUNGraph::ClassclassTUNGraph 有向图///Directedgraph.##TNGraph::ClassclassTNGraph 二部图///Bipartitegraph.##Bipartite_graphclassTBPGraph 多重图///Directedmultigr......
  • C语言笔记:第5章 运算符,表达式和语句
    基本运算符算术运算符+加法运算符-减法运算符,或负值运算符*乘法运算符/除法运算符%模运算符,或称取余运算符,要求%两侧均为整型关系运算符<小于运算符<=小于等于运算符>大于运算符>=大于等于运算符==等于运算符!=不等于运算符关系运算的结果成立就为"......
  • C# 学习笔记 - 异常
    异常概述C#的异常捕获系统允许开发者将正常代码与异常处理逻辑进行分离。异常可以表示在软件执行期间出现的各种异常情况,包括内部的和外部的。外部条件导致的异常:网络故障、权限不足、内存不足、Web服务引发的异常,这些异常通常由操作系统、.NET运行时或外部应用程序引发;内......
  • C语言笔记:第4章 字符串和格式输入输出
    字符串简介:2123   printf函数和scanf函数printf函数输出格式详解:https://www.cnblogs.com/Ahair/p/5017050.htmlhttps://www.cnblogs.com/wucongzhou/p/12668766.htmlscanf函数输入格式详解:https://www.cnblogs.com/morya/p/6067479.htmlhttps://www.cnblogs.com/wu......
  • C# 学习笔记 - 控制流
    控制流条件语句、迭代语句、跳转语句和异常处理语句控制程序的执行流。条件语句使用关键字if,switch来决定执行某些语句迭代语句使用关键字do,while,for,foreach和in创建一个循环跳转语句使用关键字break,continue,return和yield转移程序控制条件语句(Condi......
  • 飞鸟集 阅读笔记
    2023.7.1买的,好好读!一级标题是这首诗是第几首,很有感觉的摘抄在下面,有可能写点注解。1夏天的飞鸟,飞到我窗前唱歌,又飞去了。秋天的黄叶,它们没有什么可唱,只叹息一声,飞落在那里。9有一次,我们梦见大家都是不相识的。我们醒了,却知道我们原是相爱的。注记注意是“我们”,这里的......
  • PMP学习笔记(五)
    06.20星期二 项目进度网络图 员工激励:拖延症和帕金森定律。减少估算时间和磨洋工的机会 计算期望时间,默认是贝塔分布 搞清楚关键路径:总是差=0 总浮时:在不影响整体时间的前提下可以推迟的时间;自由浮时:在不影响后一个任务的启动日期的前提下可以推迟的时间 重要干系人发生变化,应首......
  • 选读SQL经典实例笔记01_检索和排序
    1. 在WHERE子句中引用别名列1.1. 当表里的某些列没有被恰当命名的时候,这个技巧尤其有用1.2. sqlselectsalassalary,commascommissionfromempwheresalary<50001.3. 内嵌视图1.3.1.  sqlselect*from(selectsalassalary,commascommission......