知识点扫盲:二分答案

本条博客以及本系列用于记录算法学习中不熟悉或少见的知识点

一.关于二分答案的来源和应用

  1. 由于本大一 == 蒟蒻 == 没有经过系统性的学习,在一场牛客小白月赛中首次听说二分答案这一操作,十分震惊其效率与作用,遂写此篇来记录

  2. 关于二分答案有关的概念[二分法]"https://oi-wiki.org//basic/binary/#二分法"

  3. 简单来说:解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。若满足单调性,则满足使用二分法的条件。把这里的枚举换成二分,就变成了「二分答案」。

  4. 需要注意:二分法的应用场景:满足单调性或需要最大值最小化。
    注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 1,不满足看做 0,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。

要求满足某种条件的最大值的最小可能情况(最大值最小化),首先的想法是从小到大枚举这个作为答案的「最大值」,然后去判断是否合法。若答案单调,就可以使用二分搜索法来更快地找到答案。因此,要想使用二分搜索法来解这种「最大值最小化」的题目,需要满足以下/三个条件/:

1.答案在一个固定区间内;

2.可能查找一个符合条件的值不是很容易,但是要求能比较容易地判断某个值是否是符合条件的;

3.可行解对于区间满足一定的单调性。换言之,如果 x 是符合条件的,那么有 x + 1 或者 x - 1 也符合条件。(这样下来就满足了上面提到的单调性)
当然,最小值最大化是同理的。

二、经典例题(难度不大)

e.g1. [洛谷p2440木材加工]"https://www.luogu.com.cn/problem/P2440"

e.g2. [P4058 [Code+#1] 木材]"https://www.luogu.com.cn/problem/P4058"

[Code+#1] 木材

题目描述

有 $n$ 棵树,初始时每棵树的高度为 $H_i$,第 $i$ 棵树每月都会长高 $A_i$。现在有个木料长度总量为 $S$ 的订单,客户要求每块木料的长度不能小于 $L$,而且木料必须是整棵树(即不能为树的一部分)。现在问你最少需要等多少个月才能满足订单。

输入格式

第一行 $3$ 个用空格隔开的非负整数 $n,S,L$,表示树的数量、订单总量和单块木料长度限制。

第二行 $n$ 个用空格隔开的非负整数,依次为 $H_1,H_2, ... ,H_n$。

第三行 $n$ 个用空格隔开的非负整数,依次为 $A_1,A_2, ... ,A_n$。

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

3 74 51
2 5 2
2 7 9

样例输出 #1

7

提示

对于样例,在六个月后,各棵树的高度分别为 $ 14,47,56 $,此时无法完成订单。

在七个月后,各棵树的高度分别为 $ 16,54,65 $,此时可以砍下第 $ 2 $ 和第 $ 3 $ 棵树完成订单了。

以下为eg2的代码及本人在2024.11.10因两个测试点数十次wonderful answer 后的经验与教训

```
#include<bits/stdc++.h>//需要对n==1时进行特判
#define N 200005
#define mod 998244353
#define int unsigned long long
using namespace std;
typedef long long ull;
int n,s,l;
//priority_queue<int,vector<int>,greater<int> >q;
int a[N],b[N],c[N];
bool check(int x)
{
    int b2 = x;
    for(int i = 1; i <= n;i++)
    {
        c[i] = x*b[i] + a[i];
    }
    int sum = 0;
    for(int i = 1;i <= n;i++)
    {
        if(c[i] >= l)   sum += c[i];
    }
    if(sum >= s) return true;
    else    return false;
}
void solve()
{
    cin>>n>>s>>l;
    for(int i = 1;i <= n;i++)   cin>>a[i];
    for(int i = 1;i <= n;i++)   cin>>b[i];
    if(n == 1)
    {
        c[1] = a[1];
        if(s <= a[1])
        {
            cout<<0;
        }
        else
        {
            ull ans = 0;
            ans = (s-a[1])/b[1];
            cout<<ans<<"\n";
        }    
        return ;
    }
    int l = 0,r = 1e18;//此处应注意,l不设置-1的原因是我使用了ull
    while(l < r)
    {
        int mid = (l+r)/2;
        if(check(mid))  r = mid;
        else    l = mid+1;
    }
    cout<<l<<"\n";
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    solve();
    return 0;
}
```

image.png

image.png

但是关于此处,本人还是不太理解,为何将l<=r与r = mid-1 改为 l < r 与 r = mid就能将tle的#16过掉 (是宇宙射线

e.g3 [小红打怪]"https://ac.nowcoder.com/acm/contest/94879/C"

/本题为萌新较难看出来的二分答案~~其实是因为我太蒟蒻~~/


注意力不太集中的同学也可以观察到,因为本题单调性显然成立,或者说,满足

需要将最大值最小化,假设最少第x次可以将所有怪物击杀,那么第x+1显然能

将所有怪物击杀,因此可以使用二分答案的方式进行处理;


贪心有关的内容:先欠着

容易得到代码

```
#include<bits/stdc++.h>
#define N 200005
#define mod 998244353
#define int long long//恶毒的写法,不要轻易使用
using namespace std;
typedef long long ll;
int n,a[N],b[N];
bool check(int x)
{
    int b2 = x;
    for(int i = 1;i <= n;i++)
    {
        b[i] = max(a[i]-b2,0ll);//因为怪物血量不能为0
    }
    int t = 0x3f3f3f;
    for(int i =1;i <= n;i++)
    {
        t = min(b[i],b[i+1]);
        b2 -= t;
        b[i] -=t;
        b[i+1] -=t;
    }
    b2 +=x;//易得,若相邻的次数有剩,则剩下的即为单体打击
    for(int i =1 ;i <= n;i++)
    {
        b2 -=b[i];
    }
    return b2>=0;//最后若为真,则说明目前的回合数位于x右侧
}
void solve()
{
    cin>>n;
    for(int i = 1;i <= n;i++)   cin>>a[i];
    int l=0,r = 1e9;//这里的1e9与数据范围有关
    while(l <= r)
    {
        int mid = (l+r)/2;
        if(check(mid))   r = mid-1;
        else    l = mid+1;
    }
    cout<<l<<"\n";
}
signed main()
{
    ios::sync_with_stdio(false);//关闭流同步
    cin.tie(nullptr);//此处注意
    cout.tie(nullptr);//若报错,则说明c++版本过低,~~建议更换c++20或c++23~~
    solve();
    return 0;
}
```
posted @   graspppp  阅读(108)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示