二分查找 & 二分答案 详解 & 例题

二分查找

基础思想

对于一个 (非严格)单调 序列,我们可以采用二分查找的方法在 O ( l o g 2 n ) 内 O(log_2n)内 O(log2n)寻找出某个元素的位置。
一般的代码是:

//对于非升序列A
int l = 1, r = n;
while(l <= r) {
	int m = (l + r) / 2;
	if(A[m] == k) {
		ans = m;
		break;
	}
	if(A[m] < k) 
		l = m + 1;
	else r = m - 1;
}

写二分时,最麻烦的不是其本身,还是初始边界(l、r的初始值的确定)和结束条件(while的括号里的内容),这些需要我们多加考虑。多测试,多手玩,就不会出错。

例题

这玩意没啥例题

二分答案

具体过程

二分答案和二分查找很像,当答案有单调性时可以使用它。
具体代码

//对于答案有非升的单调性
//即对于m答案成立的话,(1~m-1)一定成立
int l = 1, r = n;
while(l <= r) {
	int m = (l + r) / 2;
	if(check(m)) {
		ans = m; 
		l = m + 1;
	}
	else r = m - 1;
}

类似地,考虑边界与l、r的变化!多测试,多手玩!

二分答案例题

LGOJ P1182 数列分段 Section II
LGOJ P1462 通往奥格瑞玛的道路
LGOJ P2678 跳石头
LGOJ P3957 跳房子

&lt;think&gt;我们被要求讨论C++中二分查找二分答案的区别与联系,并通过例题进行分析。 根据引用[2]:二分查找只是单纯的查找,而二分答案则更复杂一些。二分查找通常用于在有序数组中查找特定元素,而二分答案通常用于解决最优化问题(如最大最小、最小最大等)。 区别与联系: 1. 二分查找(Binary Search): 目的:在一个有序数组中查找某个特定元素是否存在,或者找到该元素的位置(包括第一个出现、最后一个出现等变种)。 核心:通过不断将搜索范围减半来快速定位目标。 关键:控制边界条件,避免死循环。通常有两种模板(闭区间和开区间)以及两种边界处理方式(如查找第一个大于等于target的位置,或最后一个小于等于target的位置)。 2. 二分答案(Binary Search on Answer): 目的:解决最优化问题,即在一个答案范围内(通常答案具有单调性)通过二分法来快速找到最优解(如满足条件的最大或最小)。 核心:将问题转化为判定问题(即给定一个答案mid,判断它是否可行)。通过二分这个答案范围,不断缩小区间直到找到最优解。 联系:二分答案可以看作是二分查找的一种应用,它是在一个连续的答案区间(而不仅仅是数组索引)上进行二分查找例题解析: 例1二分查找(查找第一个大于等于x的位置) 引用[1]中给出了一个闭区间二分查找的例子:在数组中查找第一次大于等于元素target的位置。 代码(参考引用[1]): int l = 0, r = n-1; // 闭区间 [l, r] while (l &lt;= r) { int mid = l + (r - l) / 2; if (arr[mid] &gt;= target) { r = mid - 1; // 尝试继续向左找 } else { l = mid + 1; } } // 最后l指向第一个大于等于target的位置 但引用[1]中提到的另一种写法(模板1): while (l &lt; r) { int mid = (l + r) &gt;&gt; 1; if (check(mid)) r = mid; // 如果mid满足条件,则答案在mid左边(包括mid) else l = mid + 1; } // 结束时l==r,即为答案 例2:二分答案(奶牛晒衣服问题,洛谷P1843) 引用[4]中给出了一个二分答案例题:奶牛晒衣服。 问题描述:有n件衣服,每件衣服有湿度c[i]。自然风干每秒减少a点湿度,使用烘干机每秒减少b点湿度(烘干机一次只能烘一件)。问最少需要多少秒(记为x)可以让所有衣服都干(湿度&lt;=0)? 分析:我们可以二分答案x(即时间),然后判断在x秒内是否能让所有衣服干。 判定函数check(x): 对于每件衣服,自然风干x秒可以减少a*x点湿度。如果剩余湿度&lt;=0,则不需要使用烘干机。 否则,剩余湿度为c[i] - a*x,这部分需要用烘干机来烘干。由于烘干机每秒减少b点,那么需要烘干的时间为 ceil((c[i]-a*x)/b)(向上取整)。 然后计算所有衣服需要烘干的总时间,如果总时间&lt;=x(因为可以同时使用多个烘干机?注意:题目中烘干机一次只能烘一件,但是我们可以多件同时进行,因为有多台烘干机吗?题目没有说,但实际上一台烘干机只能烘一件,所以总烘干时间不能超过x,而且多件衣服可以同时烘,所以要求的是每件衣服烘干时间之和?不对,这里实际上我们同时只能烘一件,所以总时间应该是每件衣服烘干时间的累加?但题目中并没有说有多台烘干机,所以默认只有一台?这样理解的话,那么总烘干时间就是各件衣服烘干时间之和,但是要求这个总和小于等于x?不对,因为一台烘干机在同一时间只能烘一件,所以烘干机的工作时间不能超过x,但是可以同时进行自然风干。所以实际上,我们可以在x秒内,用烘干机烘若干件衣服,只要每件衣服的烘干时间(注意,自然风干同时进行)满足要求,并且烘干机总工作时间不超过x(因为一台烘干机最多工作x秒)?然而,题目中并没有说只有一台烘干机,实际上我们可以认为有足够多的烘干机(或者题目允许任意使用烘干机,但同一时间一台只能烘一件,但我们可以同时使用多台?)题目没有明确,但引用[4]的代码中,是把每件衣服需要烘干的时间分别计算,然后求和,只要总时间&lt;=x,就说明x秒内可以完成。这相当于假设有多台烘干机,可以同时烘多件衣服(因为每件衣服的烘干时间可以叠加,且同时进行)。所以,实际上我们需要的烘干机台数没有限制?那么总烘干时间就是各件衣服需要烘干的时间之和(因为可以同时烘),所以总烘干时间必须&lt;=x。 但是,重新读题:题目中说&ldquo;使用烘干机每秒减少b点湿度(烘干机一次只能烘一件)&rdquo;,这意味着同一时间只能烘一件衣服。那么,如果有多件衣服需要烘干,我们必须顺序使用烘干机?这样总时间就是每件衣服烘干时间之和?那么总时间必须大于等于每件衣服烘干时间之和。然而,在x秒内,我们最多只能使用烘干机x秒(因为一台烘干机只能工作x秒,而且我们只有一台),所以要求每件衣服的烘干时间之和&lt;=x?不对,因为同一时间只能烘一件,所以烘干机总工作时间就是各件衣服烘干时间的累加,所以要求这个累加和&lt;=x。 因此,check(x)函数: 对于每件衣服i,计算自然风干x秒后剩余的湿度:c[i] - a*x。 如果&lt;=0,则不需要烘干。 否则,需要烘干的时间为 ceil((c[i]-a*x)/b)(因为烘干机每秒减少b点湿度)。 然后,计算所有衣服需要烘干的时间总和,如果总和&lt;=x,则x秒可行。 然后我们二分x,找到最小的x使得check(x)为真。 引用[4]的代码: bool check(int x){ int sum=0;//使用洗衣机的总时间 for(int i=1;i&lt;=n;i++){ if(c[i]&lt;=x*a) continue;//能自然晾干 else{//使用洗衣机 if((c[i]-x*a)%b==0) sum+=(c[i]-x*a)/b; else sum+=(c[i]-x*a)/b+1; } } if(sum&lt;=x) return true; else return false; } 注意:这里判断总烘干时间sum是否&lt;=x,因为x秒内,烘干机最多只能工作x秒(即使有多台,但题目没有说,代码中这样处理相当于假设有足够多的烘干机,可以同时烘,所以总时间就是每件衣服烘干时间的最大?不对,这里代码中计算的是总和,而且题目说一次只能烘一件,所以实际上我们只能顺序烘,那么总时间应该是每件衣服烘干时间的累加?但是,如果我们有足够多的烘干机,那么就可以同时烘,这样总时间就是所有衣服中烘干时间最长的那个?所以这里题目可能有不同的理解。但根据洛谷P1843的题目描述,实际上我们只有一台烘干机,所以必须顺序烘,因此总烘干时间应该是每件衣服烘干时间的累加。但是,在x秒内,我们同时进行自然风干,而烘干机只能烘一件,所以其他衣服在等待烘干时也在自然风干。因此,我们不能简单地将烘干时间累加,因为烘干一件衣服的同时,其他衣服也在自然风干。所以,上述代码中的方法(累加)实际上是不正确的?但为什么能AC呢? 重新思考:题目中并没有说在烘干一件衣服时,其他衣服不能自然风干。所以,在烘干机工作的同时,自然风干对所有衣服都起作用。因此,我们并不需要顺序烘,而是可以任意安排烘干的时间段(只要总烘干时间不超过x,并且同一时间只能烘一件)。那么,我们实际上需要的是:每件衣服的烘干时间(即烘干机占用时间)可以任意安排在x秒内的任意时间段,同时自然风干一直在进行。所以,对于一件衣服,我们只需要在x秒内分配一些时间段给它用烘干机(总时长为t_i),那么这件衣服减少的湿度为:a*x + b*t_i。要求 a*x + b*t_i &gt;= c[i] =&gt; t_i &gt;= ceil((c[i]-a*x)/b) 当c[i]-a*x&gt;0时。同时,由于烘干机同一时间只能烘一件,所以所有衣服的烘干时间段不能重叠。但是,因为我们可以任意安排时间段(只要不重叠),那么总烘干时间就是所有衣服的t_i之和。而总烘干时间不能超过x(因为只有一台烘干机,最多工作x秒)。所以,要求就是:所有衣服的t_i之和&lt;=x。 因此,上述代码中的累加是合理的。 所以,二分答案的步骤: 1. 确定答案范围:l=0, r=可能的最大时间(比如最大湿度乘以某个系数,或者直接设一个较大的数,如引用[4]中r=N=500010)。 2. while (l&lt;=r) { mid = (l+r)/2; if (check(mid)) { // 如果mid时间可行,则尝试更小的时间 ans = mid; r = mid-1; } else { l = mid+1; } } 3. 输出ans。 总结: 二分查找:在有序数组中查找特定元素,通常直接对数组索引进行二分二分答案:在连续的可能答案区间(通常具有单调性)上二分,通过check函数判断当前答案是否可行,从而缩小区间。 联系:二分答案可以看作是在一个虚拟的&ldquo;有序数组&rdquo;(答案范围)上进行的二分查找,这个数组的&ldquo;索引&rdquo;就是连续的数,而&ldquo;元素&rdquo;是否满足条件由check函数决定。&lt;/think&gt;### C++ 二分查找二分答案的区别与联系及例题解析 #### **一、核心区别** 1. **目的不同** - **二分查找**:在**有序数组**中快速定位目标(如查找元素位置、判断是否存在)。 **本质**:通过缩小搜索范围快速定位目标。 **时间复杂度**:$O(\log n)$。 - **二分答案**:解决**最优化问题**(如&quot;最大最小化&quot;或&quot;最小最大化&quot;),在**答案范围**内二分搜索最优解。 **本质**:将问题转化为判定问题(给定答案 $mid$,检查是否可行)。 **时间复杂度**:$O(\log R)$,$R$ 为答案范围。 2. **操作对象不同** - 二分查找:直接操作**数组索引**(离散)。 - 二分答案:操作**连续答案区间**(如时间、长度等连续)。 3. **核心函数** - 二分查找:比较目标与 $arr[mid]$。 - 二分答案:依赖 `check(mid)` 函数(用户自定义的可行性判定逻辑)。 #### **二、联系** - **共享二分框架**:两者均通过**缩小区间**逼近结果,代码结构相似(如循环条件、边界更新)。 - **单调性要求**:均要求问题具有**单调性**(数组有序或答案可行性与大小单调相关)。 --- #### **三、例题解析** ##### **例题1二分查找(查找第一个 $\geq x$ 的位置)** **问题**:在有序数组 `arr` 中找到第一个 $\geq x$ 的索引。 **代码实现**(闭区间模板): ```cpp int binary_search(vector&lt;int&gt;&amp; arr, int x) { int l = 0, r = arr.size() - 1; while (l &lt;= r) { int mid = l + (r - l) / 2; if (arr[mid] &gt;= x) r = mid - 1; // 满足条件,向左缩小区间 else l = mid + 1; // 不满足条件,向右缩小区间 } return l; // 返回第一个&ge;x的位置 } ``` **关键点**: - 当 `arr[mid] &gt;= x` 时,说明答案在左半区间(包括 $mid$),故更新 $r = mid - 1$。 - 循环结束时 $l$ 即为目标位置[^1]。 --- ##### **例题2:二分答案(洛谷 P1843 奶牛晒衣服)** **问题**:$n$ 件衣服的湿度为 $c[i]$,自然风干每秒减少 $a$ 点湿度,烘干机每秒减少 $b$ 点湿度(一次只能烘一件)。求最小时间 $x$ 使所有衣服湿度 $\leq 0$[^4]。 **分析**: - **答案范围**:$x \in [0, \text{max\_time}]$(如 $[0, 5 \times 10^5]$)。 - **单调性**:若 $x$ 时间可行,则 $\geq x$ 的时间均可行。 - **判定函数**:`check(x)` 计算在 $x$ 秒内,烘干机所需总时间是否 $\leq x$。 **代码实现**: ```cpp bool check(int x, vector&lt;int&gt;&amp; c, int a, int b) { long long sum = 0; // 烘干机总使用时间 for (int i = 0; i &lt; c.size(); i++) { if (c[i] &lt;= (long long)a * x) continue; // 自然风干即可 int remain = c[i] - a * x; // 剩余湿度 sum += (remain % b == 0) ? remain / b : remain / b + 1; // 向上取整 } return sum &lt;= x; // 烘干机总时间&le;x则可行 } int main() { int n, a, b; cin &gt;&gt; n &gt;&gt; a &gt;&gt; b; vector&lt;int&gt; c(n); for (int i = 0; i &lt; n; i++) cin &gt;&gt; c[i]; int l = 0, r = 500000, ans = 0; while (l &lt;= r) { int mid = l + (r - l) / 2; if (check(mid, c, a, b)) { ans = mid; // 记录可行解 r = mid - 1; // 尝试更小时间 } else { l = mid + 1; // 时间不足,需增大 } } cout &lt;&lt; ans &lt;&lt; endl; return 0; } ``` **关键点**: - **`check(mid)` 逻辑**: - 自然风干 $mid$ 秒后,若 $c[i] \leq a \times mid$,无需烘干机。 - 否则需烘干机补充,时间按 $b$ 的倍数向上取整。 - **二分更新**:若 `check(mid)` 为真,说明 $mid$ 可行,尝试更小时间($r = mid - 1$);否则增大时间($l = mid + 1$)[^4]。 --- #### **四、对比总结** | **维度** | **二分查找** | **二分答案** | |----------------|----------------------------------|----------------------------------| | **问题类型** | 有序数据查找 | 最优化问题(如极问题) | | **操作对象** | 数组索引(离散) | 答案范围(连续) | | **核心函数** | 比较 `arr[mid]` 与目标 | 自定义 `check(mid)` 判定可行性 | | **模板差异** | 直接比较元素 | 依赖外部判定函数 | | **典型应用** | 查找元素、边界位置 | 最小最大化、最大最小化 | --- ### 相关问题 1. **二分查找中如何避免死循环?** 关键点:确保每次循环区间缩小(如更新 $l = mid + 1$ 而非 $l = mid$),并统一使用闭区间或左闭右开区间[^1][^2]。 2. **二分答案的 `check` 函数设计有哪些技巧?** 需满足:① 单调性;② 高效计算(通常 $O(n)$ 或 $O(n \log n)$)。例如背包问题中判定是否能在某容量内装所有物品。 3. **如何处理二分答案中的边界问题?** 初始化 $l$ 为理论最小,$r$ 为理论最大(如 $10^{18}$)。循环结束后 $ans$ 即为最优解[^3][^4]。 4. **二分查找二分答案在实际问题中的联合应用场景?** 例:先通过二分答案确定&quot;最大间隔&quot;,再用二分查找验证该间隔是否可行(如电缆布置问题)。 [^1]: 二分查找的多种实现方式及本质解析(c++实现 + 例题) [^2]: 二分查找 &amp; 二分答案 万字详解,超多例题,带你学透二分 [^3]: C++ | 二分查找(重点二分答案) [^4]: CSP/信奥赛C++刷题训练:经典二分答案例题(2):洛谷P1843:奶牛晒衣服
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值