本条博客以及本系列用于记录算法学习中不熟悉或少见的知识点
一.关于二分答案的来源和应用
-
由于本大一 == 蒟蒻 == 没有经过系统性的学习,在一场牛客小白月赛中首次听说二分答案这一操作,十分震惊其效率与作用,遂写此篇来记录
-
关于二分答案有关的概念[二分法]"https://oi-wiki.org//basic/binary/#二分法"
-
简单来说:解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。若满足单调性,则满足使用二分法的条件。把这里的枚举换成二分,就变成了「二分答案」。
-
需要注意:二分法的应用场景:满足单调性或需要最大值最小化。
注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 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;
}
```
![alt text](image.png)
![alt text](image-1.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;
}
```
标签:二分,知识点,int,long,扫盲,满足,答案,define
From: https://www.cnblogs.com/graspppp/p/18538654