知识点扫盲:二分答案
本条博客以及本系列用于记录算法学习中不熟悉或少见的知识点
一.关于二分答案的来源和应用
-
由于本大一 == 蒟蒻 == 没有经过系统性的学习,在一场牛客小白月赛中首次听说二分答案这一操作,十分震惊其效率与作用,遂写此篇来记录
-
关于二分答案有关的概念[二分法]"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; }


但是关于此处,本人还是不太理解,为何将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; }

浙公网安备 33010602011771号