加载中...

信息奥赛|二分


信息奥赛|二分

知识点概览 🚀

  • 二分查找(基础)
  • 整数二分
  • 浮点数二分
  • 二分答案

知识点详解 🎥

二分查找(基础)

学习素材 ☘️

编号 视频内容 视频链接 平台 UP主 推荐指数
1 区间判断法 分析二分查找边界细节 链接1 bilibili 代码随想录 🌟🌟🌟🌟🌟

推荐学习路线 🚀

二分查找 的算法思想很容易理解,但是在书写具体代码时,有很多细节需要注意,如

  • 初始值 right = n 还是 right = n-1
  • 循环条件是 i<=j 还是 i<j
  • 在改变区间时,是 left = mid 还是 left=mid + 1

视频 1 从 「区间合法性判断」 的角度,对上述细节问题做出了探讨,还是值得一看的。


二分查找模版 🧑🏻‍💻

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

Leetcode 704 - 二分查找:https://leetcode.cn/problems/binary-search/

int search(int* nums, int numsSize, int target){
    int l = 0, r = numsSize - 1;
    while (l <= r) {
        int mid = l + r >> 1;
        if (nums[mid] == target) 
            return mid;
        else if (nums[mid] > target) 
            r = mid - 1;
        else 
            l = mid + 1;
    }
    return -1;
}

整数二分

学习素材 ☘️

编号 视频内容 视频链接 平台 UP主 推荐指数
1 算法基础课——整数二分 链接1 B站 大雪菜 🌟🌟🌟🌟🌟

推荐学习路线 🚀

整数二分 有两个具体的模版,观看 视频1 听 y总 讲解即可。


整数二分模版 🧑🏻‍💻

AcWing 整数二分模版:https://www.acwing.com/blog/content/31/

  • 模版1:区间 $[l, r]$ 被二分成 $[l, mid]$ 和 $[mid + 1, r]$,且需要以区间 $[l, mid]$ 继续二分时使用
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 模版1:区间[l, r]被二分成[l, mid]和[mid + 1, r],且需要以区间[l, mid]继续二分时使用
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        // 当mid满足时,若要继续往左找,答案区间从[l, r] -> [l, mid]
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
  • 模版2:区间 $[l, r]$ 被二分成 $[l, mid+1]$ 和 $[mid, r]$,且需要以区间 $[mid, r]$ 继续二分时使用
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 模版2:区间[l, r]被二分成[l, mid+1]和[mid, r],且需要以区间[mid, r]继续二分时使用
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        // 当mid满足时,若要继续往右找,答案区间从[l, r] -> [mid, r]
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

整数二分 OI案例 ✍️

【题目名称】查找
【题目描述】

输入 $n$ 个不超过 $10^9$ 的单调不减的(就是后面的数字不小于前面的数字)非负整数 $a_1,a_2,\dots,a_{n}$,然后进行 $m$ 次询问。对于每次询问,给出一个整数 $q$,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 $-1$ 。

【输入】

第一行 $2$ 个整数 $n$ 和 $m$,表示数字个数和询问次数。

第二行 $n$ 个整数,表示这些待查询的数字。

第三行 $m$ 个整数,表示询问这些数字的编号,从 $1$ 开始编号。

【输出】

输出一行,$m$ 个整数,以空格隔开,表示答案。

【数据范围】

$1≤N≤300$

【输入样例】
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
【输出样例】
1 2 -1
【数据范围】

数据保证,$1 \leq n \leq 10^6$,$0 \leq a_i,q \leq 10^9$,$1 \leq m \leq 10^5$

本题输入输出量较大,请使用较快的 IO 方式。

【原题链接】

https://www.luogu.com.cn/problem/P2249


【题目分析】

本题是整数二分一个非常经典的问题,查找第一个 x 出现的下标,需要自己按照 y总 讲解的方法推导理解一下。

当然,本题也可以使用 STL 中的 lower_bound。 它的返回值是第一个大于等于 x 的迭代器,若要转化成下标,需要减去 v.begin();若要转化成 第几个,则还要在下标的基础上 + 1。

比如对于 1 2 3 3 4 5

  • lower_bound(v.begin(),v.end(),3) 会返回第一个 3 的迭代器
  • lower_bound(v.begin(),v.end(),3) - v.begin() 会返回第一个 3 的下标
  • lower_bound(v.begin(),v.end(),3) - v.begin() + 1 会返回第一个 3 在数组中是第几个数
【C++ 代码】

代码1:整数二分

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;
int a[N];
int n, m, x;

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

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

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

        int l = 1, r = n; //确定边界
        while (l < r) {
            int mid = (l + r) / 2;
            if (a[mid] >= x) r = mid;    //r = mid,模版1
            else l = mid + 1;
        }

        //退出循环,l=r
        if (a[l] != x) cout << "-1 ";
        else cout << l << " ";
    }

    return 0;
}

代码2:STL 中的 lower_bound

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;
int n, m, x, tmp;
vector<int> v;
vector<int>::iterator it;

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    cin >> n >> m;
    //将数据读入 vector中
    for (int i = 1; i <= n; ++i) {
        cin >> tmp;
        v.push_back(tmp);
    }

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

        //lower_bound 返回指针,由于要求的下标是数值,故减去 v.begin()
        //只有vector支持这样的减法
        int idx = lower_bound(v.begin(), v.end(), x) - v.begin();

        if (v[idx] != x) cout << "-1 ";
        else cout << idx + 1 << " ";

    }

    return 0;
}

浮点数二分

学习素材 ☘️

编号 视频内容 视频链接 平台 UP主 推荐指数
1 算法基础课——浮点数二分 链接1 B站 大雪菜 🌟🌟🌟🌟🌟

推荐学习路线 🚀

浮点数二分 相比 整数二分 更加简单一些,在理解 整数二分 的基础上,再去观看 视频1,基本就没问题了。


浮点数二分模版 🧑🏻‍💻

AcWing 浮点数二分模版:https://www.acwing.com/blog/content/277/

  • 浮点数二分模版
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;
}

浮点数二分 OI案例 ✍️

【题目名称】查找
【题目描述】

有函数:$f(x)=x^5−15x^4+85x^3−225x^2+274x−121$

已知 $f(1.5) \gt 0,f(2.4) \lt 0$ 且方程 $f(x)=0$ 在区间 $[1.5,2.4]$ 有且只有一个根,请用二分法求出该根。

【输入】

【输出】

该方程在区间 $[1.5,2.4]$中的根。要求四舍五入到小数点后 $6$ 位。

【数据范围】

【输入样例】

【输出样例】

【原题链接】

http://ybt.ssoier.cn:8088/problem_show.php?pid=1241


【题目分析】

函数零点数的平方根 等问题是 浮点数二分 一些非常经典的问题,在 观看视频1 后,只需要在理解模版的基础上使用模版即可。

🍉 PS:一般题目要求输出 6 位小数,则 lr 的误差取 const double eps=1e-8,一般再往后两位。

🍉 PS2:慎用 C/C++ 的 pow 函数,有时候会有坑,详见其他博客。

【C++ 代码】
#include<bits/stdc++.h>

using namespace std;

const double eps = 1e-8;  //定义误差

double f(double x);  //定义函数

int main() {

    double l = 1.5, r = 2.4;
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (f(mid) <= 0) r = mid;   //原函数在 [1.5,2.4] 递减
        else l = mid;
    }

    cout << fixed << setprecision(6) << l << endl;

    return 0;
}

double f(double x) {
    return x * x * x * x * x - 15 * x * x * x * x + 85 * x * x * x - 225 * x * x + 274 * x - 121;
}

二分答案

二分答案 是通过对答案的二分,来降低时间复杂度的算法,其前提是「二段性」。

二分答案这类题目在初学阶段会比较困难,初学者往往很难理解二分答案的过程。

但是这类题目存在一个「质变」过程,常常发生在做题或者思考的某个瞬间。在那个瞬间,会有种 拨云见日 的感觉,仿佛一下子理解了二分的本质,此后做这类题就会顺畅许多。

所以笔者的建议是照着题单多刷题(见文末「刷题题单」)、多理解(尤其是 check 函数怎么写,二分模版怎么选取等)让自己早日达到这个质变的瞬间。


刷题题单 🧑🏻‍💻

更多题目详见刷题题单

二分查找与整数二分:信息奥赛题单|整数二分🔗 / (备用)信息奥赛题单|整数二分🔗

浮点数二分:信息奥赛题单|浮点数二分🔗 / (备用)信息奥赛题单|浮点数二分🔗

二分答案:信息奥赛题单|二分答案🔗 / (备用)信息奥赛题单|二分答案🔗

如链接失效,可以在文章末尾选择上一篇博客 或 在搜索界面搜索关键词「二分」即可。

参考资料 📚


文章作者: Rickyの水果摊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Rickyの水果摊 !
  目录