判断题:顺序查找(线性查找)= 从数组第一个元素开始,逐个与目标值比较,直到找到目标或查完整个数组为止。
考点:顺序查找的定义(A1)。
解析:顺序查找(线性查找)= 从第一个元素开始逐个与目标比较,找到即停,查完还没找到就是"不存在"。✅ 正确
排除法:无(判断题)。混淆点:顺序查找不需要数据有序,这是它与二分查找最大的前提差异(B2)。
关联 · 顺序查找的适用场景(A2):无序数据只能用顺序查找——这是它不可替代的原因。
判断题:当数据无序、数据量较小、或数据存放在链表上(无法随机访问)时,顺序查找是合适的选择。
考点:顺序查找的适用场景(A2)。
解析:三种典型场景:① 数据无序;② 数据量小( 也很快);③ 存储在链表等无随机访问的结构上(A6)。✅ 正确
排除法:无(判断题)。混淆点:数据有序且大时顺序查找就吃亏了,改用二分(B5)。
关联 · 链表只能顺序查找(A6):链表不能下标跳转,顺序查找是唯一选择。
单选题:在 个元素的数组中顺序查找某个值,最坏情况下需要比较多少次?
考点:顺序查找最坏比较次数(A3)。
解析:最坏 = 目标在最后一个位置或不在数组里:要把 个元素全部比较一遍,共 次。正确答案 B。
排除法:A( 次)是"成功查找的平均次数"(A4);C()是二分查找的复杂度;D( 次)是最佳情况(第一个就是)。
关联 · 成功查找的平均比较次数(A4):最坏与平均是两个概念,别混。
单选题:在 个元素中顺序查找,且一定能找到(目标均匀分布在各位置),平均比较次数约为?
考点:成功查找的平均比较次数(A4)。
解析:目标在每个位置等概率时,比较次数为 ,平均 。正确答案 A。
排除法:B( 次)是最坏情况;C()是二分复杂度;D()不可能是线性扫描的量级。
关联 · 顺序查找最坏比较次数(A3):平均 、最坏 ,都是 级别。
判断题:C++ 数组下标从 开始:a[0] 是第一个元素,循环 for (int i = 0; i < n; i++) 恰好访问全部 个元素。
考点:数组下标从 0 开始(A5)。
解析:C++ 数组下标 :a[0] 是第一个元素,for (int i = 0; i < n; i++) 恰好访问全部 个元素。✅ 正确
排除法:无(判断题)。混淆点:边界写 i <= n 会访问 a[n] 越界(P3 同源错误)。
关联 · 查找失败的返回值(G3):下标从 0 开始,所以"找不到"常用 -1 而不是 0。
判断题:链表不支持随机访问(不能直接跳到中间节点),因此在链表上查找某个值只能用顺序查找——从头节点开始逐个向后走。
考点:链表只能顺序查找(A6)。
解析:链表只能通过指针从头向后逐个走(无随机访问),因此查找必须顺序进行:。✅ 正确
排除法:无(判断题)。混淆点:链表上"二分"无从谈起——取不到中点(B7)。
关联 · 链表不能高效二分(B7):二分需要 跳到中间元素,链表做不到。
单选题:关于顺序查找与二分查找,下列说法正确的是?
考点:顺序查找与二分查找的前提(A7)。
解析:二分查找的前提是数据有序;顺序查找无此要求。正确答案 C。
排除法:A 说反(二分恰恰要求有序);B 说反(顺序查找不要求有序);D 张冠李戴( 是二分的复杂度,顺序是 )。
关联 · 二分查找的前提条件(B2):有序是二分成立的地基。
判断题:二分查找的基本思想:每次取当前区间的中间元素与目标比较,根据比较结果把查找区间缩小一半,直到找到目标或区间为空。
考点:二分查找的基本思想(B1)。
解析:每次取区间中点与目标比较,根据大小关系把区间砍掉一半,重复直到找到或区间为空——典型的分治思想。✅ 正确
排除法:无(判断题)。混淆点:"砍一半"靠的是有序:中点一边全小、另一边全大(B2)。
关联 · 每次排除一半的原因(F1):规模每次减半 → 。
单选题:二分查找要求数组满足什么条件?
考点:二分查找的前提条件(B2)。
解析:数组必须有序(升序或降序均可,比较方向相反而已)。正确答案 B。
排除法:A 说反;C(元素互不相同)不必要——有重复也能二分(C4/C5);D(个数偶数)不必要——任何 都能二分。
关联 · 降序数组的二分(G2):降序时收缩方向翻转。
单选题:在升序数组 a 中二分查找 x,当前区间为 [l, r],中点 mid。若 a[mid] < x,下一步应该在哪个区间找?
考点:比较后区间的收缩方向(B3)。
解析:升序数组里 a[mid] < x 说明 x 只可能在右侧(更大的那边),区间收缩为 [mid + 1, r]。正确答案 B。
排除法:A 方向反(左侧元素更小,不可能有 x);C 是找到时才做的动作;D 是查完区间为空时的结论。
关联 · 收缩时排除 mid(C3):
mid已经比较过,收缩必须把它排除。
单选题:在 个元素的有序数组中二分查找,最坏情况下最多比较约多少次?
考点:二分查找的比较次数(B4)。
解析:每次砍半, 个数最多 次比较就剩 个()。正确答案 A。
排除法:B( 次)是 量级的误解;C( 次)是顺序查找的平均次数(A4);D( 次)是顺序查找的最坏次数(A3)。
关联 · 对数规模的估算(F2): 次、 次,同款估算。
判断题:数据量大且有序时,二分查找 远快于顺序查找 ;例如 时,二分最多约 次比较,顺序平均约 次。
考点:二分与顺序的速度对比(B5)。
解析: 时二分最多约 次比较,顺序平均约 次——差距约 25000 倍。✅ 正确
排除法:无(判断题)。混淆点:这个优势只在"有序 + 多次查找"时兑现(F3/F4)。
关联 · 两种查找的比较次数对比(F6): 越大,差距越悬殊。
判断题:二分的本质不是"对着数组切半",而是对具有单调性的判断条件不断收缩可行区间——二分答案正是利用这一点,对答案的取值范围二分。
考点:二分的本质是收缩区间(B6)。
解析:二分 = 对单调的判断条件收缩可行区间:数组有序 → "在左/右"可判定;答案可行性单调 → "取 mid 可行/不可行"可判定。两者同源。✅ 正确
排除法:无(判断题)。混淆点:以为二分只能查数组——二分答案(D1)就是它的推广。
关联 · 二分答案的定义(D1):把"下标区间"换成"答案区间"。
判断题:链表也能高效地二分查找,因为每次只要从当前节点向后走一半的距离。
考点:链表不能高效二分(B7)。
解析:二分每步要 取中点,链表取中点要 遍历——整体退化回 ,与顺序查找相当,失去二分意义。❌ 错误(原句说法错误)
排除法:无(判断题)。混淆点:"向后走一半距离"本身就要 步,每次砍半的收益被走路成本吃掉。
关联 · 二分查找的局限(F5):有序 + 随机访问,两个条件缺一不可。
单选题:设 l、r 是闭区间 [l, r] 的端点,且它们的值可能接近 。计算中点 mid 的推荐写法是?
考点:中点写法防溢出(C1)。
解析:l + (r - l) / 2 先算区间长度(不溢出)再折半,是标准写法;(l + r) / 2 在 l、r 接近 时加法溢出。正确答案 B。
排除法:A 是易错写法(H2/K1/P2 的祸根);C 把除 2 写成乘 2,完全错误;D l/2 + r/2 在两者皆奇数时会少 1。
关联 · 中点加法的溢出(H2): 级别相加即溢出。
判断题:闭区间写法 while (l <= r) 中,l == r 时区间里还剩 个元素必须检查,所以循环条件要带等号;写 while (l < r) 会漏掉最后一个元素。
考点:闭区间的循环条件(C2)。
解析:闭区间 [l, r] 中 l == r 时还剩 个元素,必须再检查一次,所以条件是 l <= r。✅ 正确
排除法:无(判断题)。混淆点:左闭右开 [l, r) 才用 l < r(J6/J7),两套写法要配套使用(P3)。
关联 · 区间开闭不统一(P3):闭区间配
<或开区间配<=都会出问题。
单选题:升序数组 a 中二分查找 x,若 a[mid] > x,说明 x 只可能在哪个区间?
考点:收缩时排除 mid(C3)。
解析:mid 已比较过,a[mid] > x 时 x 在 [l, mid - 1];同理 a[mid] < x 时在 [mid + 1, r]。正确答案 B。
排除法:A 方向反;C([l, mid])没排除 mid,等于白比较一次、可能死循环(H3);D 无意义。
关联 · 收缩方向写错(H3):不排除
mid是二分最常见的 bug。
判断题:数组中有重复元素时,普通二分找到的"等于 x"的位置不一定是第一个;要找第一个等于 x 的位置,需要特殊处理——等于时也继续向左收缩。
考点:找第一个等于 x 的位置(C4)。
解析:有重复元素时,普通二分命中的"等于 x"位置不保证是第一个;要找第一个,命中后继续向左收缩(r = mid - 1 并记录 ans)。✅ 正确
排除法:无(判断题)。混淆点:lower_bound(第一个 )能直接定位第一个等于 x 的位置(C6)。
关联 · 第一次出现的位置(K4):
a[mid] >= x时记 ans 并r = mid - 1。
单选题:升序数组 a = {1, 2, 2, 2, 3, 3, 5},最后一个等于 2 的元素下标是(下标从 开始)?
考点:找最后一个等于 x 的位置(C5)。
解析:数组 {1, 2, 2, 2, 3, 3, 5} 中等于 的下标是 ,最后一个是下标 。正确答案 C。
排除法:A()是第一个;B()是中间那个;D()是 第一次出现的位置。
关联 · 最后一次出现的位置(K5):命中等值时向右收缩。
单选题:升序数组 a = {1, 3, 3, 5, 7, 9},其中第一个大于等于 的元素是?
考点:lower_bound 的概念(C6)。
解析:lower_bound = 第一个大于等于 x 的位置。{1, 3, 3, 5, 7, 9} 中 的第一个是 ( 都小于 )。正确答案 C。
排除法:A()小于 不满足;B()数组里没有,答案必须是数组元素;D()不是"第一个"。
关联 · upper_bound 的概念(C7):只差一个等号: 与 。
单选题:升序数组 a = {1, 3, 3, 5, 7, 9},其中第一个大于 的元素是?
考点:upper_bound 的概念(C7)。
解析:upper_bound = 第一个大于 x 的位置。{1, 3, 3, 5, 7, 9} 中 的第一个是 (两个 都不大于 )。正确答案 C。
排除法:A()小于 ;B()不大于 ;D()不是"第一个"。
关联 · 二分统计出现次数(O2):
upper_bound - lower_bound= 出现次数。
判断题:二分答案 = 答案的取值范围已知、且"可行性"随答案单调变化时,对答案的值二分:每次用 check(mid) 判断"答案取 mid 是否可行",据此收缩答案范围。
考点:二分答案的定义(D1)。
解析:答案的取值范围已知、可行性随答案单调时,对答案二分:check(mid) 判"答案取 mid 是否可行",可行/不可行各砍掉一半区间。✅ 正确
排除法:无(判断题)。混淆点:二分答案的复杂度是 ,check 内部还要跑一遍 或贪心。
关联 · 二分答案的基本框架(D3):主循环就三种写法,背熟一种。
判断题:二分答案的前提是可行性单调——例如答案越小越容易可行(可行与不可行各连成一段);没有这个性质就不能二分答案。
考点:二分答案的单调性前提(D2)。
解析:可行性必须随答案单调:例如"砍到 H 得木材 ≥ M",H 越小越容易可行——可行/不可行各成一段,二分才找得到分界点。✅ 正确
排除法:无(判断题)。混淆点:没有单调性的问题(可行性忽真忽假)二分会漏解或错解。
关联 · 砍树问题的单调性(E1):木材量随 H 单调递减。
单选题:二分答案求最大可行答案的常见框架是?
考点:二分答案的基本框架(D3)。
解析:求最大可行答案:可行时 mid 要保留 → l = mid;mid 向下取整会让区间卡死,所以中点用 (l + r + 1) / 2。正确答案 A。
排除法:B 是"最小可行答案"的框架(可行 r = mid、中点向下取整);C 是枚举不是二分;D 没有利用单调性。
关联 · 死循环:l 与 mid 的关系(H1):
l = mid配向下取整 = 死循环。
单选题:以下哪类问题最适合用二分答案解决?
考点:二分答案的题目特征(D4)。
解析:"最小的最大值 / 最大的最小值"类最优化问题(砍树求最大高度、跳石头求最大最小间距),答案越界可行/不可行单调,是二分答案的主场。正确答案 B。
排除法:A、C 一次扫描/累加即可,不必二分;D 与二分无关。
关联 · 跳石头问题模型(E2):最小值最大化 → 最大可行答案框架。
判断题:二分答案中 check(mid) 的作用是判断"答案取 mid 时能否满足题目的限制条件";可行则 mid 保留在答案区间内继续试探,不可行则排除 mid 及更差的一半。
考点:check 函数的作用(D5)。
解析:check(mid) = 判断"答案取 mid 能否满足限制"。可行 → mid 留在区间内继续试探;不可行 → mid 及更差的一半全部排除。✅ 正确
排除法:无(判断题)。混淆点:check 写得越高效,二分答案总时间越少——它内部常是贪心或模拟(E6)。
关联 · check 内部的贪心验证(E6):验证"最短间距 ≥ mid"用贪心数移走的石头。
判断题:二分答案的答案可能很大(如高达 甚至 ),l、r、mid 及相关求和计算应使用 long long,否则加法可能溢出。
考点:二分答案的数据类型(D6)。
解析:答案可达 以上时,l + r、mid 运算、check 内求和都可能超出 int——统一用 long long 最稳妥。✅ 正确
排除法:无(判断题)。混淆点:CSP-J 二分答案题常卡 int 溢出(例如砍树求和 可达 )。
关联 · 中点加法的溢出(H2):
(l + r) / 2的溢出就是类型问题。
判断题:二分查找是对"数组下标区间"二分,二分答案是对"答案取值区间"二分——两者思想相同(利用单调性收缩区间),对象不同。
考点:二分答案与二分查找(D7)。
解析:二分查找在"下标区间"上二分(有序数组),二分答案在"答案取值区间"上二分(可行性单调)——思想相同、对象不同。✅ 正确
排除法:无(判断题)。混淆点:二分答案常借用二分查找的边界写法(上取整/下取整、开闭区间),坑也一样(H1/P3)。
关联 · 二分的本质是收缩区间(B6):两种二分是同一个思想的两个应用。
单选题:砍树问题:有 棵树,第 棵高 。把每棵树从地面往上砍到统一高度 (比 矮的树不砍),要求得到的木材总长度至少 ,求最大的 。若 减小,总木材长度会?
考点:砍树问题的单调性(E1)。
解析:总木材 。 减小 → 每棵树砍下的部分变多 → 总木材增加(单调递减函数)。正确答案 C。
排除法:A 方向反;B 只在个别树高恰好等于 的瞬间不变;D 错——单调性是确定的。
关联 · 砍树二分答案(L1):木材量单调 → 二分最大 。
单选题:跳石头问题:一条河上有若干石头(起点与终点固定),移走其中 块后,要使相邻石头(含起点、终点)之间的最小距离尽可能大。这是哪类模型?
考点:跳石头问题模型(E2)。
解析:目标是最小间距尽可能大 = 最小值最大化,典型的二分答案模型:check(mid) 数一数"间距 需移走几块", 即可行。正确答案 A。
排除法:B(最大值最小化)是另一个方向的模型;C 与场景无关;D 贪心直接取最大间隔没有正确性保证——必须二分 + 验证。
关联 · 跳石头二分答案(L2):贪心验证 + 二分主循环。
单选题:分巧克力问题: 块巧克力边长分别为 ,全部切成边长为 mid 的正方形(不能拼接),要求至少得到 块。当 mid 变大时,能切出的总块数会?
考点:分巧克力问题(E3)。
解析:每块 切 块。mid 变大 → 每块切得少 → 总数变少(单调递减)。正确答案 B。
排除法:A 方向反;C/D 忽略了整除的单调性。
关联 · 分巧克力二分答案(L3):
cnt(mid) >= K找最大边长。
判断题:求 的近似值也可以用二分答案:在区间 上二分,check(mid) 判断 mid * mid <= 2,不断缩小区间直到足够精确。
考点:浮点二分求平方根(E4)。
解析: 单调递增,mid^2 <= 2 的"可行域"是 ——把二分查找的框架套到实数上即可。✅ 正确
排除法:无(判断题)。混淆点:实数二分没有"相等",靠精度或迭代次数终止(E5)。
关联 · 实数二分求平方根(M1):60 次迭代足够精确到 15 位小数。
单选题:浮点二分通常用什么作为终止条件?
考点:浮点二分的终止条件(E5)。
解析:浮点几乎不会精确相等,标准写法是 while (r - l > eps)——区间长度 时停止,误差不超过 。正确答案 B。
排除法:A(l == r)可能永远不成立;C(循环 次)与数据个数无关;D 是二分查找的比较次数概念,不适用。
关联 · 固定次数迭代(M3):不想调 eps 就固定迭代 50~100 次。
判断题:二分答案的 check(mid) 内部常用贪心验证可行性——例如验证"最短间距能否 "时,间距不够就移走石头,最后看移走的数量是否 。
考点:check 内部的贪心验证(E6)。
解析:验证"最短间距能否 ":从起点开始,遇到间距不够的石头就移走(贪心:移得越靠前留给后面越多),最后数移走数量 即可行。✅ 正确
排除法:无(判断题)。混淆点:贪心只在 check 内部做验证用,答案本身仍靠二分找。
关联 · 跳石头二分答案(L2):
cnt(mid)就是这段贪心。
单选题:二分查找是 而顺序查找是 ,根本原因是?
考点:每次排除一半的原因(F1)。
解析:二分每次比较排除一半元素:,共约 步——这就是 的来源。正确答案 A。
排除法:B(一次比较排除一个)是顺序查找的节奏,;C、D 与二分无关。
关联 · 二分查找的比较次数(B4): 次、 次。
单选题:在 个元素的有序数组中二分查找,最坏比较次数约为?
考点:对数规模的估算(F2)。
解析:,所以 规模最多约 次比较。正确答案 A。
排除法:B()是 的误解;C/D 都是线性或平方量级,与二分无关。
关联 · 两种查找的比较次数对比(F6):规模翻倍只多 1 次比较。
单选题:数据无序且只需要查找 次,最划算的做法是?
考点:单次查找的选择(F3)。
解析:只查 1 次时,排序 是纯开销:直接顺序查找 更快。正确答案 B。
排除法:A 排序后二分总代价 ;C 哈希建表也是 但常数大、小题大做;D 二分要求有序,无序时直接二分不可信(H4)。
关联 · 多次查找的选择(F4):查询次数多时排序的一次性成本被摊薄。
单选题:数据无序但要反复查找很多次(查询次数很大),最划算的做法是?
考点:多次查找的选择(F4)。
解析:查询 次( 大):顺序查找 ;先排序 再每次二分 , 大时后者完胜。正确答案 B。
排除法:A 每次 总量 太慢;C 每次查询都排序纯浪费;D 无正确性。
关联 · 排序后多次二分(O1):
sort一次 + 循环内二分。
判断题:二分查找的局限:要求数据有序且支持随机访问;若数据经常插入、删除,维护有序状态的开销会很大。
考点:二分查找的局限(F5)。
解析:二分要求有序 + 随机访问;数据频繁插入/删除时,每次维护有序都要移动 元素,此时平衡树/哈希更合适(CSP-J 只需理解局限即可)。✅ 正确
排除法:无(判断题)。混淆点:二分快是有代价的——代价就是"有序"这个前提。
关联 · 二分查找的前提条件(B2):前提不满足,二分失效。
单选题: 时,顺序查找平均比较约 次、二分最坏约 次;当 时,二分最坏比较次数约为?
考点:两种查找的比较次数对比(F6)。
解析:,所以 时二分最坏约 次;顺序平均约 次。正确答案 A。
排除法:B/C/D 分别是 的 、、 量级,都不是对数规模。
关联 · 二分与顺序的速度对比(B5): 越大差距越大。
单选题:C++ 标准库中 lower_bound(a, a + n, x) 返回的是?
考点:lower_bound 与 upper_bound(G1)。
解析:lower_bound(a, a+n, x) 返回第一个 的位置(迭代器);upper_bound 返回第一个 的位置。正确答案 B。
排除法:A 把"大于等于"缩成"等于";C 是"最后一个小于"(等价 lower_bound - 1);D 是"次数"(=upper - lower,O2)。
关联 · 二分统计出现次数(O2):两者相减即出现次数。
大纲注:lower_bound/upper_bound不在入门级 STL 白名单,属二分查找的常用工具。
单选题:降序数组 a = {9, 7, 5, 3, 1} 中二分查找 4,若 a[mid] < 4,说明 4 应该在?
考点:降序数组的二分(G2)。
解析:降序数组左大右小:a[mid] < 4 说明当前位置已小于 ,更大的在左侧(下标更小的方向)。正确答案 A。
排除法:B 是升序数组的方向(降序恰好相反);C 草率——还要继续找;D 未比较完不能定论。
关联 · 降序数组的二分(J4):收缩方向与升序完全相反。
判断题:手写二分查找找不到目标时,通常返回 -1 表示"不存在";若返回 0,会被误认为是"在下标 0 处找到"。
考点:查找失败的返回值(G3)。
解析:下标从 开始, 是合法下标,所以"找不到"必须用 之类的非法值,不能与合法下标冲突。✅ 正确
排除法:无(判断题)。混淆点:STL 的 lower_bound 找不到时返回"尾后"迭代器 a+n,与手写 是两种约定。
关联 · 数组下标从 0 开始(A5):下标约定决定哨兵值的选择。
判断题:求 ( 为非负整数)可以用二分:在 中找最大的 mid 满足 mid * mid <= x。
考点:二分也能求平方根(G4)。
解析:mid * mid <= x 的可行性随 mid 单调:在 中找最大的 mid 满足 mid * mid <= x,即 。✅ 正确
排除法:无(判断题)。混淆点:mid * mid 要用 1LL 防溢出(K7)。
关联 · 二分求整数平方根(K7):上取整中点 +
l = mid框架。
判断题:二分查找可以写成递归(对半区间递归调用)或迭代(while 循环)两种形式,时间复杂度都是 。
考点:递归与迭代写法(G5)。
解析:递归版(对半区间递归)与迭代版(while 循环)等价,复杂度都是 ;递归多一层函数调用开销。✅ 正确
排除法:无(判断题)。混淆点:递归版注意 l > r 为终止条件;迭代版注意循环条件与收缩配套(C2/C3)。
关联 · 递归二分查找(J5):递归版代码示例。
单选题:二分代码 while (l < r) { mid = (l + r) / 2; if (check(mid)) l = mid; else r = mid - 1; } 求最大可行答案时,会出现什么问题?
考点:死循环:l 与 mid 的关系(H1)。
解析:mid = (l + r) / 2 向下取整,当 l = r - 1 时 mid == l;此时若 check 为真执行 l = mid,区间毫无变化 → 死循环。正确答案 A。
排除法:B/C 不是必然结论;D 错——这是求"最大可行"时最经典的 bug。
关联 · 二分答案的基本框架(D3):
l = mid必须配mid = (l + r + 1) / 2。
单选题:l 和 r 都在 附近时,mid = (l + r) / 2 会发生什么?
考点:中点加法的溢出(H2)。
解析:l + r 两个 级别的 int 相加超过 ,先溢出再除以 2,得到错误的(甚至负的)mid。正确答案 A。
排除法:B——C++ 不会自动升级,类型由操作数决定;C——能编译,运行时才溢出;D 错,见 K1 的实际输出。
关联 · 中点写法防溢出(C1):
l + (r - l) / 2根治。
单选题:升序数组二分中,a[mid] > x 时误写成 l = mid(而不是 r = mid - 1),会导致?
考点:收缩方向写错(H3)。
解析:a[mid] > x 时 mid 已排除,却写 l = mid:区间含着一个已比较过的位置,可能反复检查同一位置甚至死循环。正确答案 A。
排除法:B/C 说反——错误写法只会更慢或卡死;D 无保证。
关联 · 收缩时排除 mid(C3):比较过的
mid必须排除。
判断题:数组未排序时直接二分查找,结果不可信——可能碰巧返回正确位置,也可能返回错误位置。
考点:无序数组不能二分(H4)。
解析:二分依赖"中点一边全小、一边全大"的判定,数组无序时该判定失效:可能碰巧查对,也可能查错——结果不可信。✅ 正确
排除法:无(判断题)。混淆点:不是"一定找不到",而是"不可信",这是两回事。
关联 · 单次查找的选择(F3):无序就先顺序查找,或先排序再二分。
单选题:二分答案求最大可行答案时,check(mid) 为真却写成 r = mid - 1(把可行的 mid 排除了),会导致?
考点:check 方向写反(H5)。
解析:求最大可行答案时,check(mid) 为真应保留 mid(l = mid);写反成 r = mid - 1 把可行解丢掉,最终答案偏小甚至取到下界。正确答案 A。
排除法:B 偏大是另一种写反;C 不是必然;D 错——这是二分答案最隐蔽的 bug 之一。
关联 · check 方向写反(P5):代码实证——条件反了答案冲到上界。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {3, 7, 1, 9, 5, 2}; // 6 个元素 05 int x = 9; 06 for (int i = 0; i < 6; i++) 07 if (a[i] == x) { cout << i; break; } // 找到就输出下标并停止 08 return 0; 09}
单选题:程序输出是?
考点:顺序查找输出下标(I1)。
解析:从头扫:、、、 命中 → 输出下标 3 并 break。正确答案 A。
实现要点:顺序查找 = for 扫描 + 命中即停(break)。输出的是下标不是值——i 与 a[i] 要分清(O5 同款考点)。
排除法:B(4)把命中位置记错;C(9)输出的是值不是下标;D(2)是 a[5] 的值。
关联 · 找第一个匹配的下标(I4):从左往右扫,天然找到第一个。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {3, 7, 1, 9, 5}; 05 int x = 4; // 数组里没有 4 06 int pos = -1; // 先假设找不到 07 for (int i = 0; i < 5; i++) 08 if (a[i] == x) pos = i; 09 cout << pos; 10 return 0; 11}
单选题:程序输出是?
考点:查找失败返回 -1(I2)。
解析: 不在数组里,循环跑完 pos 仍是初值 -1,输出 -1。正确答案 A。
实现要点:找不到的约定:pos 初值设 -1,扫完没更新就返回 -1。 不会与合法下标 冲突。
排除法:B(0)会把"下标 0"与"找不到"混淆;C(4)是最后一个下标;D(5)是循环越界后的值。
关联 · 查找失败的返回值(G3): 是"找不到"的标准约定。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[8] = {2, 5, 2, 7, 2, 9, 2, 4}; 05 int x = 2, cnt = 0; 06 for (int i = 0; i < 8; i++) 07 if (a[i] == x) cnt++; // 每找到一个 x 就计数加 1 08 cout << cnt; 09 return 0; 10}
单选题:程序输出是?
考点:统计出现次数(I3)。
解析:数组里 出现在下标 ,共 次,cnt 累加到 4。正确答案 B。
实现要点:计数题 = 命中就 cnt++,不 break——与"找下标"题的关键区别。
排除法:A(3)漏数一个;C(5)多数一个;D(8)把数组长度当次数。
关联 · 统计出现次数(O2):二分版用
upper - lower求次数。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {5, 2, 7, 2, 9, 2}; 05 int x = 2; 06 for (int i = 0; i < 6; i++) 07 if (a[i] == x) { cout << i; return 0; } // 从左往右找,输出第一个 08 return 0; 09}
单选题:程序输出是?
考点:找第一个匹配的下标(I4)。
解析:从左往右扫,第一个 在下标 ,命中即 return 0 结束。正确答案 A。
实现要点:"第一个"= 按顺序扫、命中即停;如果继续扫不退出,会输出最后一个()。
排除法:B(3)是第二个 ;C(5)是最后一个 ;D(2)是值不是下标。
关联 · 第一次出现的位置(K4):有序数组用二分找第一个。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {3, 8, 1, 8, 5, 2}; 05 int p = 0; // p 记录当前最大值的下标 06 for (int i = 1; i < 6; i++) 07 if (a[i] > a[p]) p = i; // 严格大于才更新 08 cout << p; 09 return 0; 10}
单选题:程序输出是?
考点:打擂台找最大值位置(I5)。
解析:p 记录当前最大下标:; 都不大于 (> 严格),最终 p = 1。正确答案 A。
实现要点:打擂台 = 维护"当前最优位置"p,遇到更优(严格 >)才更新。用严格大于保证有重复最大值时输出第一个。
排除法:B(3)是第二个 的位置——>= 才这样;C(8)是值;D(4)是 的下标。
关联 · 顺序查找的适用场景(A2):找最值也是 扫描。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {0}; // a[5] 留作哨兵 05 for (int i = 0; i < 5; i++) cin >> a[i]; // 输入:3 7 1 9 5 06 int x = 2, i = 0; 07 a[5] = x; // 哨兵:数组末尾放 x 08 while (a[i] != x) i++; // 一定停:最坏停在哨兵处 09 if (i < 5) cout << i; 10 else cout << -1; 11 return 0; 12}
单选题:程序输出是?
考点:哨兵法查找(I6)。
解析: 放哨兵 ,while 一定停:扫过 都不等于 ,最后 i = 5 命中哨兵;i < 5 不成立 → 输出 -1。正确答案 A。
实现要点:哨兵法 = 在数组末尾放目标值,while 循环去掉越界判断 i < n;循环结束用 i < n 区分"真找到"与"撞哨兵"。
排除法:B(5)是哨兵下标,但题目输出的是"找不到"信号;C(2)是目标值;D(0)无依据。
关联 · 查找失败返回 -1(I2):哨兵法与普通写法殊途同归。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[9] = {1, 3, 5, 7, 9, 11, 13, 15, 17}; // 升序数组 05 int x = 13; 06 int l = 0, r = 8, ans = -1; 07 while (l <= r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] == x) { ans = mid; break; } 10 else if (a[mid] < x) l = mid + 1; 11 else r = mid - 1; 12 } 13 cout << ans; 14 return 0; 15}
单选题:程序输出是?
考点:标准二分查找(J1)。
解析::mid=4 得 ;mid=6 得 命中 → ans = 6。正确答案 A。
实现要点:闭区间 while (l <= r) 三件套:a[mid] == x 命中;< x 去右半 l = mid + 1;> x 去左半 r = mid - 1。手算时画区间:每次写下 l, r, mid 三个数。
排除法:B(13)是值不是下标;C(5)是中间过程中的 l;D(7)是下一轮才会查到的位置。
关联 · 闭区间的循环条件(C2):
l <= r保证最后 1 个元素也被检查。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[9] = {1, 3, 5, 7, 9, 11, 13, 15, 17}; 05 int x = 9; 06 int l = 0, r = 8; 07 while (l <= r) { 08 int mid = l + (r - l) / 2; 09 cout << mid << " "; // 输出每次检查的中点下标 10 if (a[mid] == x) break; 11 else if (a[mid] < x) l = mid + 1; 12 else r = mid - 1; 13 } 14 return 0; 15}
单选题:程序输出是?
考点:输出每次的中点(J2)。
解析:l=0, r=8,mid=4 对应 直接命中 → 只输出一次 4。正确答案 A。
实现要点:阅读"过程输出"题:把每轮 mid 值排成序列。命中即停,所以输出序列在命中处结束。
排除法:B(4 6 5)是"找不到"时继续收缩的轨迹;C 方向相反;D(9)是值。
关联 · 二分比较次数统计(J3):同款循环,改数
cnt。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[10] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}; 05 int x = 17; // 数组中不存在 17 06 int l = 0, r = 9, cnt = 0; 07 while (l <= r) { 08 int mid = l + (r - l) / 2; 09 cnt++; // 每比较一次计数加 1 10 if (a[mid] == x) break; 11 else if (a[mid] < x) l = mid + 1; 12 else r = mid - 1; 13 } 14 cout << cnt; 15 return 0; 16}
单选题:程序输出是?
考点:二分比较次数统计(J3)。
解析: 不存在:mid=4()→ l=5;mid=7()→ l=8;mid=8()→ r=7;l > r 退出,共比较 次。正确答案 A。
实现要点:失败时的比较次数 = while 循环体执行的次数;每轮 cnt++ 放循环开头。 时失败比较约 次。
排除法:B(4)多数一轮;C(2)漏数;D(10)是顺序查找最坏次数。
关联 · 二分查找的比较次数(B4): 个元素最多约 次。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[7] = {21, 17, 13, 9, 5, 3, 1}; // 降序数组 05 int x = 5; 06 int l = 0, r = 6; 07 while (l <= r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] == x) { cout << mid; return 0; } 10 else if (a[mid] < x) r = mid - 1; // 降序:当前太小,往左找更大的 11 else l = mid + 1; 12 } 13 cout << -1; 14 return 0; 15}
单选题:程序输出是?
考点:降序数组的二分(J4)。
解析:降序数组左大右小:mid=3 得 ;mid=5 得 ;mid=4 得 命中 → 输出 4。正确答案 A。
实现要点:降序二分的收缩方向与升序完全相反:a[mid] < x 时去左半(r = mid - 1),a[mid] > x 时去右半(l = mid + 1)。
排除法:B(5)是升序方向的下标;C(-1)是找不到的信号;D(3)是 的下标。
关联 · 降序数组的二分(G2):方向判断的依据是"哪边更大"。
01#include <bits/stdc++.h> 02using namespace std; 03int a[7] = {2, 5, 8, 11, 14, 17, 20}; // 升序数组 04int f(int l, int r, int x) { // 在 [l, r] 中二分查找 x 05 if (l > r) return -1; 06 int mid = l + (r - l) / 2; 07 if (a[mid] == x) return mid; 08 if (a[mid] < x) return f(mid + 1, r, x); // 递归查右半 09 return f(l, mid - 1, x); // 递归查左半 10} 11int main() { 12 cout << f(0, 6, 11); 13 return 0; 14}
单选题:程序输出是?
考点:递归二分查找(J5)。
解析:f(0, 6, 11):mid=3 得 命中 → 返回 3。正确答案 A。
实现要点:递归二分 = 三分支:命中返回 mid;太小递归右半 f(mid+1, r, x);太大递归左半 f(l, mid-1, x);l > r 是终止(返回 -1)。
排除法:B(11)是值;C(-1)是"找不到"分支的结果;D(4)是 的下标。
关联 · 递归与迭代写法(G5):递归版与
while版等价。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[8] = {1, 3, 3, 5, 5, 7, 9, 9}; // 升序数组 05 int x = 5; 06 int l = 0, r = 8; // 左闭右开 [l, r),r 是"尾后"位置 07 while (l < r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] < x) l = mid + 1; // 中点太小,答案在右侧 10 else r = mid; // a[mid] >= x,答案在 [l, mid] 11 } 12 cout << l; // 第一个 >= x 的下标 13 return 0; 14}
单选题:程序输出是?
考点:手写 lower_bound(J6)。
解析:左闭右开 [l, r):mid=4 得 ;mid=2 得 ;mid=3 得 ;l == r == 3 输出。正确答案 A。
实现要点:lower_bound 框架 = while (l < r) + 条件 a[mid] < x 时 l = mid + 1,否则 r = mid。r 取"尾后"位置 n(不是 n-1)。返回 l。
排除法:B(4)是第二个 ;C(5)是值;D(2)是第一个 。
关联 · 找第一个满足条件的下标(K2):同一框架的不同题目。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[8] = {1, 3, 3, 5, 5, 7, 9, 9}; // 升序数组 05 int x = 5; 06 int l = 0, r = 8; // 左闭右开 [l, r) 07 while (l < r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] <= x) l = mid + 1; // 中点 <= x,答案在右侧 10 else r = mid; // a[mid] > x,答案在 [l, mid] 11 } 12 cout << l; // 第一个 > x 的下标 13 return 0; 14}
单选题:程序输出是?
考点:手写 upper_bound(J7)。
解析:与 lower_bound 只差一个等号:mid=4 得 ;mid=6 得 ;mid=5 得 ;输出 l = 5。正确答案 A。
实现要点:upper_bound = lower_bound 把条件换成 a[mid] <= x。两者相减 = 出现次数(O2)。
排除法:B(4)是 lower_bound 的结果;C(3)是第一个 ;D(6)是 的下标。
关联 · 二分统计出现次数(O2):
upper - lower求次数。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int l = 1500000000, r = 1600000000; // 都接近 int 上限 05 int mid = (l + r) / 2; // 注意:l + r 会先溢出 06 cout << mid; 07 return 0; 08}
单选题:在常见 32 位 int(补码截断)环境下,程序输出是?
考点:中点溢出的实际输出(K1)。
解析:l + r :补码截断得 ,除以 得 。正确答案 B。
实现要点:int 加法溢出先于除法发生。,。防御:l + (r - l) / 2。
排除法:A(1550000000)是数学正确值,但 int 装不下中间结果;C——C++ 能编译,运行时才溢出;D 无依据。
关联 · 中点写法防溢出(C1):换写法即免疫。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[9] = {1, 3, 5, 7, 9, 11, 13, 15, 17}; // 升序数组 05 // 找第一个 >= 10 的元素下标(lower_bound 写法) 06 int l = 0, r = 9; 07 while (l < r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] < 10) l = mid + 1; 10 else r = mid; 11 } 12 cout << l; 13 return 0; 14}
单选题:程序输出是?
考点:找第一个满足条件的下标(K2)。
解析:找第一个 :mid=4 得 ;mid=7 得 ;mid=6 得 ;mid=5 得 ;输出 5。正确答案 A。
实现要点:lower_bound 框架 + 自定义条件 a[mid] < 10。套路:把"满足条件的位置集合"看成单调后缀,找后缀起点。
排除法:B(6)是第二个满足的下标;C(11)是值;D(4)是最后一个不满足的下标。
关联 · 手写 lower_bound(J6):本题就是 lower_bound 的应用。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[9] = {1, 3, 5, 7, 9, 11, 13, 15, 17}; // 升序数组 05 // 找最后一个 <= 10 的元素下标 06 int l = 0, r = 8; 07 while (l < r) { 08 int mid = (l + r + 1) / 2; // 向上取整,防止死循环 09 if (a[mid] <= 10) l = mid; // 可行则包含 mid 继续向右 10 else r = mid - 1; 11 } 12 cout << l; 13 return 0; 14}
单选题:程序输出是?
考点:上取整中点找最后一个(K3)。
解析:找最后一个 :mid=4 得 ;mid=6 得 ;mid=5 得 ;l == r == 4。正确答案 A。
实现要点:找"最后一个满足"= 可行时 l = mid(不排除 mid)+ 中点向上取整 (l + r + 1) / 2 防死循环。与 K2 的"第一个满足"(向下取整 + r = mid)成镜像。
排除法:B(5)是第一个 的下标;C(9)是值;D(6)是 的下标。
关联 · 死循环:l 与 mid 的关系(H1):
l = mid必须配上取整中点,本题是正确示范。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[10] = {2, 2, 3, 3, 3, 5, 5, 7, 7, 7}; // 升序,有重复 05 int x = 3; 06 int l = 0, r = 9, ans = -1; 07 while (l <= r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] >= x) { ans = mid; r = mid - 1; } // 记下并继续向左找 10 else l = mid + 1; 11 } 12 cout << ans; // x 第一次出现的位置 13 return 0; 14}
单选题:程序输出是?
考点:第一次出现的位置(K4)。
解析:找第一个 :mid=4 得 → ans=4, r=3;mid=1 得 ;mid=2 得 → ans=2, r=1;退出,ans = 2。正确答案 A。
实现要点:a[mid] >= x 时先记录 ans = mid 再向左收缩 r = mid - 1——命中了也别停,继续找更靠左的。
排除法:B(4)是第一次命中的位置(没继续找);C(3)是中间那个;D(5)是 的下标。
关联 · 找第一个等于 x 的位置(C4):普通二分只保证"命中",不保证"第一个"。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[10] = {2, 2, 3, 3, 3, 5, 5, 7, 7, 7}; // 升序,有重复 05 int x = 3; 06 int l = 0, r = 9, ans = -1; 07 while (l <= r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] <= x) { ans = mid; l = mid + 1; } // 记下并继续向右找 10 else r = mid - 1; 11 } 12 cout << ans; // x 最后一次出现的位置 13 return 0; 14}
单选题:程序输出是?
考点:最后一次出现的位置(K5)。
解析:找最后一个 :mid=4 得 → ans=4, l=5;mid=7 得 ;mid=5 得 ;退出,ans = 4。正确答案 A。
实现要点:与 K4 镜像:a[mid] <= x 时记录并向右收缩 l = mid + 1。两次二分(K4+K5)相减加 1 = 出现次数。
排除法:B(2)是第一个 ;C(5)是 第一次出现;D(6)是第二个 。
关联 · 找最后一个等于 x 的位置(C5):概念题 C5 的代码实现。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {1, 4, 4, 9, 12, 15}; // 升序数组 05 int x = 10; // 若插入 x,应该放在哪个下标? 06 int l = 0, r = 6; // 左闭右开,r 可等于 6 07 while (l < r) { 08 int mid = l + (r - l) / 2; 09 if (a[mid] < x) l = mid + 1; 10 else r = mid; 11 } 12 cout << l; // 第一个 >= x 的位置即插入位置 13 return 0; 14}
单选题:程序输出是?
考点:二分找插入位置(K6)。
解析:找第一个 的下标即插入位置:mid=3 得 ;mid=5 得 ;mid=4 得 ;输出 4。正确答案 A。
实现要点:插入位置 = lower_bound(x)。注意 r 初始是 n(尾后位置)——插入到末尾时下标恰好是 n。
排除法:B(3)是最后一个 的下标;C(5)是尾后位置;D(10)是值。
关联 · lower_bound 与 upper_bound(G1):STL 同名函数返回迭代器。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int x = 50; 05 // 求最大的整数 s 满足 s*s <= x(即 floor(sqrt(50))) 06 int l = 0, r = x; 07 while (l < r) { 08 int mid = (l + r + 1) / 2; 09 if (1LL * mid * mid <= x) l = mid; // 1LL 防止乘法溢出 10 else r = mid - 1; 11 } 12 cout << l; 13 return 0; 14}
单选题:程序输出是?
考点:二分求整数平方根(K7)。
解析:找最大 mid 满足 mid*mid <= 50:, → 答案 7。正确答案 A。
实现要点:二分答案套到"整数平方根":上取整中点 + l = mid;1LL * mid * mid 防 int 溢出( 就超了)。
排除法:B(8)平方已超 ;C(25)是 x/2 的误解;D(6)平方 不是最大。
关联 · 二分也能求平方根(G4):整数版用
l = mid框架。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int h[4] = {20, 15, 10, 17}; // 4 棵树的高度 05 int M = 7; // 至少需要 7 米木材 06 auto wood = [&](int H) { // 都砍到高度 H,能得到多少木材 07 long long s = 0; 08 for (int i = 0; i < 4; i++) 09 if (h[i] > H) s += h[i] - H; 10 return s; 11 }; 12 int l = 0, r = 1000000000; // 答案范围 [0, 10^9] 13 while (l < r) { 14 int mid = (l + r + 1) / 2; 15 if (wood(mid) >= M) l = mid; // 砍到 mid 够用,试着砍更高 16 else r = mid - 1; 17 } 18 cout << l; // 最大的可行高度 19 return 0; 20}
单选题:程序输出是?
考点:砍树二分答案(L1)。
解析:wood(15) = 5 + 0 + 0 + 2 = 7 >= 7 可行;wood(16) = 4 + 0 + 0 + 1 = 5 < 7 不可行 → 最大 。正确答案 A。
实现要点:check(H) 求 ;主循环上取整中点 + wood(mid) >= M 时 l = mid。手算技巧:只验证"答案附近"的 即可。
排除法:B(16)木材不够();C(14)可行但不是最大;D(17)木材 更不够。
关联 · 砍树问题的单调性(E1):
wood随 单调递减。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int d[5] = {2, 11, 14, 17, 21}; // 石头到起点的距离(起点 0) 05 int L = 25, M = 2; // 终点距离 25,最多移走 2 块石头 06 auto cnt = [&](int mid) { // 使相邻间距 >= mid,需移走几块 07 int last = 0, c = 0; 08 for (int i = 0; i < 5; i++) 09 if (d[i] - last < mid) c++; // 间距不够,移走这块 10 else last = d[i]; 11 if (L - last < mid) c++; // 最后一段到终点也不够 12 return c; 13 }; 14 int l = 1, r = L; 15 while (l < r) { 16 int mid = (l + r + 1) / 2; 17 if (cnt(mid) <= M) l = mid; // 移走数量在限额内,间距可更大 18 else r = mid - 1; 19 } 20 cout << l; // 最大的最小间距 21 return 0; 22}
单选题:程序输出是?
考点:跳石头二分答案(L2)。
解析:cnt(4):间距要求 ,移走 (间距 )和 (间距 )共 块 ≤ 2 → 可行;cnt(5) = 3 > 2 不可行 → 最大 4。正确答案 A。
实现要点:check(mid) 贪心:last 记上一块保留的石头,d[i] - last < mid 就移走 d[i](last 不动),最后检查终点段。二分主循环同 L1。
排除法:B(5)需移 块超限额;C(11)是石头坐标;D(3)可行但非最大。
关联 · 跳石头问题模型(E2):最小值最大化 → 最大可行框架。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int h[3] = {6, 7, 8}; // 3 块巧克力的边长 05 int K = 10; // 至少切出 10 块 06 auto cnt = [&](int mid) { // 边长 mid 时能切几块 07 int s = 0; 08 for (int i = 0; i < 3; i++) 09 s += (h[i] / mid) * (h[i] / mid); 10 return s; 11 }; 12 int l = 1, r = 100000; 13 while (l < r) { 14 int mid = (l + r + 1) / 2; 15 if (cnt(mid) >= K) l = mid; 16 else r = mid - 1; 17 } 18 cout << l; // 最大的可行边长 19 return 0; 20}
单选题:程序输出是?
考点:分巧克力二分答案(L3)。
解析:cnt(3) = 4 + 4 + 4 = 12 >= 10 可行;cnt(4) = 1 + 1 + 4 = 6 < 10 不可行 → 最大边长 3。正确答案 A。
实现要点:check(mid) = Σ (h[i] / mid)^2(整除);cnt(mid) >= K 时 l = mid。手算只验 三个值。
排除法:B(4)块数 ;C(2)可行非最大;D(12)是 时的块数,不是边长。
关联 · 分巧克力问题(E3):块数随边长单调递减。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 // f(x) = 2x + 3 单调不减,求最大的 x 使 f(x) <= 25 05 long long l = 0, r = 1000000; 06 while (l < r) { 07 long long mid = (l + r + 1) / 2; 08 if (2 * mid + 3 <= 25) l = mid; 09 else r = mid - 1; 10 } 11 cout << l; 12 return 0; 13}
单选题:程序输出是?
考点:单调函数二分求最大值(L4)。
解析:,最大整数解 11。正确答案 A。
实现要点:把"函数值 常数"当成 check(mid): 单调不减 → 可行域是前缀 → 用"最大可行"框架(上取整中点 + l = mid)。
排除法:B(12)时 ;C(25)是常数不是 ;D(10)可行非最大。
关联 · 二分答案的定义(D1):
check不一定是数组问题,函数也成立。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int h[3] = {10, 8, 6}; // 3 棵树的高度 05 int M = 6; // 至少需要 6 米木材 06 auto wood = [&](int H) { 07 int s = 0; 08 for (int i = 0; i < 3; i++) 09 if (h[i] > H) s += h[i] - H; 10 return s; 11 }; 12 int l = 0, r = 1000000, ans = 0; 13 while (l <= r) { 14 int mid = (l + r) / 2; 15 if (wood(mid) >= M) { ans = mid; l = mid + 1; } // 可行:记下并试更大 16 else r = mid - 1; 17 } 18 cout << ans; // ans 记录法:循环结束后输出记录的最大可行值 19 return 0; 20}
单选题:程序输出是?
考点:ans 记录法的二分答案(L5)。
解析:wood(6) = 4 + 2 + 0 = 6 >= 6 可行 → ans=6, l=7;wood(7) = 3 + 1 + 0 = 4 < 6 → r=6;退出,ans = 6。正确答案 A。
实现要点:ans 记录法 = while (l <= r) 普通中点,可行时 ans = mid; l = mid + 1。优点:中点写法不用上取整;答案在 ans 里。
排除法:B(7)木材 不可行;C(5)可行非最大;D(0)是 ans 初值。
关联 · 二分答案的基本框架(D3):D3 是
l = mid派,本题是ans派,等价。
判断题:二分答案中 r 必须设成"答案可能取到的最大值"(check(r) 不一定为真也没关系);若 r 设得比真实答案还小,就会漏掉可行答案。
考点:二分答案的上界设定(L6)。
解析:r 必须是答案可能取到的最大值(如砍树的最高树高、跳石头的总长度),check(r) 为假没关系——二分过程会收缩到可行区。✅ 正确
排除法:无(判断题)。混淆点:r 设小了,比 r 大的可行答案被排除在区间外,永远找不到。
关联 · 砍树二分答案(L1):本题
r = 10^9就是答案上界。
01#include <bits/stdc++.h> 02using namespace std; 03int a[6] = {4, 8, 15, 16, 23, 42}; 04bool check(int mid) { // 数组中 >= mid 的元素是否至少 3 个 05 int cnt = 0; 06 for (int i = 0; i < 6; i++) 07 if (a[i] >= mid) cnt++; 08 return cnt >= 3; 09} 10int main() { 11 cout << check(15); 12 return 0; 13}
单选题:程序输出是?
考点:check 函数统计元素(L7)。
解析:数组中 的有 共 个,4 >= 3 成立 → 输出 1。正确答案 A。
实现要点:check 内部最常见形态 = 循环统计 + 与目标比较,返回 bool。cout << check(15) 输出布尔值 1/0。
排除法:B(0)是"不满足"的输出;C(4)是统计个数不是布尔值;D(15)是参数。
关联 · 补全 check 统计(N6):同款统计的填空版。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 double x = 2.0; // 求 sqrt(2) 05 double l = 0, r = x; 06 for (int i = 1; i <= 60; i++) { 07 double mid = (l + r) / 2; 08 if (mid * mid <= x) l = mid; 09 else r = mid; 10 } 11 cout << fixed << setprecision(4) << l; 12 return 0; 13}
单选题:程序输出是?
考点:实数二分求平方根(M1)。
解析:60 次迭代后区间长度 ,l 与 几乎重合,保留 4 位小数输出 1.4142。正确答案 A。
实现要点:实数二分 = 整数二分去掉"":mid*mid <= x 时 l = mid,否则 r = mid。没有 mid + 1,因为实数是连续的要保留 mid。
排除法:B(1.4143)四舍五入方向错;C(2.0000)是上界;D(1.4000)精度完全不够。
关联 · 浮点二分求平方根(E4):概念题的代码实现。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 double l = 1.0, r = 2.0; // sqrt(2) 在 [1, 2] 内 05 const double eps = 1e-4; // 精度:区间长不超过 1e-4 就停 06 while (r - l > eps) { 07 double mid = (l + r) / 2; 08 if (mid * mid <= 2) l = mid; 09 else r = mid; 10 } 11 cout << fixed << setprecision(3) << (l + r) / 2; 12 return 0; 13}
单选题:程序输出是?
考点:精度终止条件(M2)。
解析:while (r - l > 1e-4) 停时区间长 ,(l+r)/2 与 误差 ,保留 3 位输出 1.414。正确答案 A。
实现要点:eps 版终止 = 区间足够窄就停;答案取 (l + r) / 2 比取 l 误差更小。eps 取比输出精度小两个数量级最稳(输出 3 位 → eps )。
排除法:B(1.415)超出误差范围;C(2.000)是上界;D(1.500)是第一轮中点。
关联 · 浮点二分的终止条件(E5):
r - l > eps是标准写法。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 double l = 0, r = 3; // sqrt(2) 在 [0, 3] 内 05 for (int i = 1; i <= 50; i++) { // 迭代 50 次,精度足够 06 double mid = (l + r) / 2; 07 if (mid * mid <= 2) l = mid; 08 else r = mid; 09 } 10 cout << fixed << setprecision(6) << l; 11 return 0; 12}
单选题:程序输出是?
考点:固定次数迭代(M3)。
解析:50 次迭代把 压到 ,l 从下方逼近 ,保留 6 位小数输出 1.414214。正确答案 A。
实现要点:固定次数版 = 不用调 eps:初始区间长 ,迭代 次误差 。、 时误差 ,6 位小数绰绰有余。
排除法:B(1.414213)截断而非四舍五入;C(2.000000)是上界;D(1.500000)是第一轮中点。
关联 · 精度终止条件(M2):固定次数与 eps 两种流派,效果等价。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 // 求 x^3 + x - 2 = 0 在 [0, 2] 内的根(该函数单调递增) 05 double l = 0, r = 2; 06 for (int i = 1; i <= 60; i++) { 07 double mid = (l + r) / 2; 08 if (mid * mid * mid + mid - 2 <= 0) l = mid; 09 else r = mid; 10 } 11 cout << fixed << setprecision(4) << l; 12 return 0; 13}
单选题:程序输出是?
考点:二分求方程根(M4)。
解析: 单调递增,,根就是 ;60 次迭代后 l 逼近 ,输出 1.0000。正确答案 A。
实现要点:单调方程求根 = 判断 f(mid) <= 0 决定收缩方向(与平方根同构:f(mid) = mid*mid - 2)。先验证 保证根在区间内。
排除法:B(1.5214)是 的根,方程记混;C(0.5000)无依据;D(2.0000)是上界。
关联 · 浮点二分求平方根(E4):平方根是"方程求根"的特例。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 double x = 2.25; // 求 sqrt(2.25) 05 double l = 0, r = 2; 06 for (int i = 1; i <= 40; i++) { 07 double mid = (l + r) / 2; 08 if (mid * mid <= x) l = mid; 09 else r = mid; 10 } 11 cout << fixed << setprecision(2) << l; 12 return 0; 13}
单选题:程序输出是?
考点:浮点输出精度(M5)。
解析:,l 从下方逼近到 ,fixed << setprecision(2) 四舍五入输出 1.50。正确答案 A。
实现要点:l 永远比真值小一点点,所以输出精度位数不够时会看到 1.49 而不是 1.50;fixed + setprecision 是四舍五入,本题恰好进位成 1.50。
排除法:B(1.49)是截断(不舍入)的结果;C(2.25)是输入;D(1.00)是整数平方根的误解。
关联 · 固定次数迭代(M3):迭代次数不足时会看到
1.49,次数足够则1.50。
单选题:用浮点二分求 ()的平方根 时,初始区间通常设为?
考点:浮点二分起点(M6)。
解析: 一定在 内: 时 ; 时 。正确答案 A。
排除法:B(l = -x)下界为负, 白算一半;C(l = 1)在 时把 ()排除在外;D(r = 1)在 时把解排除在外。
关联 · 实数二分求平方根(M1):M1 用
r = x = 2是安全的()。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[8] = {5, 2, 9, 7, 3, 8, 1, 6}; 05 int x = 7; 06 int i = 0; 07 while (i < 8 && a[i] != x) ______; 08 cout << i; // 找到则输出下标,找不到输出 8 09 return 0; 10}
单选题:横线处应填入?
考点:补全顺序查找(N1)。
解析:while 条件"没越界且没找到"时向前推进 → 填 i++。运行: 命中,i 停在 3。正确答案 A。
实现要点:顺序查找 while 版 = 两个条件短路求值:i < 8 && a[i] != x(先判界再取值,顺序不能反)。i++ 是唯一能让循环前进的选项。
排除法:B(i--)向后走且可能下标越界;C(x++)改目标值,永远找不到;D(a[i]++)改数组内容,循环可能不停。
关联 · 顺序查找输出下标(I1):for 版与 while 版等价。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[7] = {1, 2, 4, 8, 16, 32, 64}; // 升序数组 05 int x = 32; 06 int l = 0, r = 6, ans = -1; 07 while (______) { 08 int mid = (l + r) / 2; 09 if (a[mid] == x) { ans = mid; break; } 10 else if (a[mid] < x) l = mid + 1; 11 else r = mid - 1; 12 } 13 cout << ans; 14 return 0; 15}
单选题:横线处应填入?
考点:补全二分循环条件(N2)。
解析:闭区间 [0, 6] 写法,条件填 l <= r。运行:mid=3 得 ;mid=5 得 命中 → 输出 5。正确答案 A。
实现要点:闭区间(r = n-1)配 l <= r;若填 l < r 会漏查最后 1 个元素(C2)。填完后按 C 组三件套走:命中停、太小右移、太大左移。
排除法:B(l < r)可能漏查;C(l != r)等价于 < 也是漏查;D 与区间大小无关、不通用。
关联 · 闭区间的循环条件(C2):
l == r时还剩 1 个元素。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[8] = {2, 3, 5, 7, 11, 13, 17, 19}; // 升序数组 05 int x = 13; 06 int l = 0, r = 7; 07 while (l <= r) { 08 int mid = ______; 09 if (a[mid] == x) { cout << mid; return 0; } 10 else if (a[mid] < x) l = mid + 1; 11 else r = mid - 1; 12 } 13 return 0; 14}
单选题:横线处应填入?
考点:补全中点计算(N3)。
解析:中点安全写法填 l + (r - l) / 2。运行:mid=3 得 ;mid=5 得 命中 → 输出 5。正确答案 A。
实现要点:中点 = 区间长度折半加到左端:l + (r - l) / 2,比 (l + r) / 2 防溢出(C1)。
排除法:B(l + r)不是中点且可能越界;C((r - l) / 2)丢了左端点,是"偏移"不是位置;D(l * r / 2)毫无依据。
关联 · 中点写法防溢出(C1):
l + (r - l) / 2是标准答案。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[9] = {1, 3, 5, 7, 9, 11, 13, 15, 17}; // 升序数组 05 int x = 5; 06 int l = 0, r = 8; 07 while (l <= r) { 08 int mid = (l + r) / 2; 09 if (a[mid] == x) { cout << mid; return 0; } 10 else if (a[mid] < x) l = ______; 11 else r = ______; 12 } 13 cout << -1; 14 return 0; 15}
单选题:两处横线依次应填入?
考点:补全区间收缩(N4)。
解析:升序数组:a[mid] < x 去右半 → l = mid + 1;a[mid] > x 去左半 → r = mid - 1。运行:mid=4 得 ;mid=1 得 ;mid=2 得 命中 → 输出 2。正确答案 A。
实现要点:收缩口诀"排除 mid":左移 mid - 1、右移 mid + 1——比较过的 mid 不再进入新区间(C3)。
排除法:B(mid、mid)不排除已比较位置,可能死循环(H3);C 方向写反;D 无依据。
关联 · 收缩时排除 mid(C3):两边各排除一个已比较位置。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int h[4] = {20, 15, 10, 17}; // 4 棵树的高度 05 int M = 7; // 至少需要 7 米木材 06 int l = 0, r = 1000000000; 07 while (l < r) { 08 int mid = (l + r + 1) / 2; 09 long long s = 0; 10 for (int i = 0; i < 4; i++) 11 if (h[i] > mid) s += h[i] - mid; 12 if (______) l = mid; // 木材够 → 试着砍更高 13 else r = mid - 1; 14 } 15 cout << l; 16 return 0; 17}
单选题:横线处应填入?
考点:补全二分答案判定(N5)。
解析:木材够用(s >= M)说明 mid 可行,保留并试更高 → 填 s >= M。运行同 L1:输出 15。正确答案 A。
实现要点:二分答案主循环的判定 = check(mid):可行 → l = mid(上取整中点配套);不可行 → r = mid - 1。判定方向写反 = P5 的灾难。
排除法:B(s <= M)方向反,答案冲到上界(P5);C(s == M)要求恰好等于,多数情况永假;D 与题目无关。
关联 · 砍树二分答案(L1):同一题的填空版。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {10, 20, 30, 40, 50}; 04bool check(int mid) { // 数组中大于 mid 的元素是否至少 2 个 05 int cnt = 0; 06 for (int i = 0; i < 5; i++) 07 if (______) cnt++; 08 return cnt >= 2; 09} 10int main() { 11 cout << check(25); 12 return 0; 13}
单选题:横线处应填入?
考点:补全 check 统计(N6)。
解析:"大于 mid 的元素"统计条件填 a[i] > mid。运行: 大于 共 个,3 >= 2 → 输出 1。正确答案 A。
实现要点:check 内部 = 循环统计 + 阈值比较。把"题目限制"翻译成"计数条件":本题限制"至少 2 个大于 mid"→ 条件 a[i] > mid、返回 cnt >= 2。
排除法:B(a[i] < mid)统计的是小于 的元素( 个也凑巧 ,但语义错);C(==)一个都没有;D 条件方向反、统计对象错。
关联 · check 函数统计元素(L7):L7 是读代码,本题是补全。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 double l = 0, r = 2; // sqrt(2) 在 [0, 2] 内 05 const double eps = 1e-6; 06 while (______) { 07 double mid = (l + r) / 2; 08 if (mid * mid <= 2) l = mid; 09 else r = mid; 10 } 11 cout << fixed << setprecision(4) << (l + r) / 2; 12 return 0; 13}
单选题:横线处应填入?
考点:补全浮点终止条件(N7)。
解析:区间长度还大于 eps 就继续二分 → 填 r - l > eps。运行:停时 (l+r)/2 与 误差 ,输出 1.4142。正确答案 A。
实现要点:浮点二分终止条件 = r - l > eps(区间足够窄才停);eps 比输出精度小两个数量级(输出 4 位 → )。
排除法:B(r - l == 0)浮点几乎永远不成立 → 死循环;C/D 与精度无关,循环次数不可控。
关联 · 精度终止条件(M2):同款终止条件的补全版。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {5, 1, 4, 2, 3, 6}; 05 sort(a, a + 6); // 排序后数组:1 2 3 4 5 6 06 int q[3] = {4, 7, 1}; // 依次查询 3 个数 07 for (int k = 0; k < 3; k++) { 08 int l = 0, r = 5, ans = -1; 09 while (l <= r) { 10 int mid = (l + r) / 2; 11 if (a[mid] == q[k]) { ans = mid; break; } 12 else if (a[mid] < q[k]) l = mid + 1; 13 else r = mid - 1; 14 } 15 cout << ans << " "; // 查到输出下标,查不到输出 -1 16 } 17 return 0; 18}
单选题:程序输出是?
考点:排序后多次二分(O1)。
解析:排序后数组 {1,2,3,4,5,6}:查 → 下标 3;查 → -1;查 → 下标 0。输出 3 -1 0。正确答案 A。
实现要点:多次查询的套路 = sort 一次 + 循环内二分。手算先写排序结果,再逐个套二分三件套。
排除法:B(2 -1 0)把 的下标少算 1(排序后 在下标 );C(3 6 0)找不到时误输出了值;D(3 -1 1)把 的下标当 (应是 )。
关联 · 多次查找的选择(F4):排序一次、二分多次是标准策略。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[8] = {1, 3, 3, 5, 5, 7, 9, 9}; // 升序数组 05 int x = 5; 06 // L:第一个 >= x 的下标(lower_bound) 07 int L = 0, R = 8; 08 while (L < R) { 09 int mid = (L + R) / 2; 10 if (a[mid] < x) L = mid + 1; 11 else R = mid; 12 } 13 // L2:第一个 > x 的下标(upper_bound) 14 int L2 = 0, R2 = 8; 15 while (L2 < R2) { 16 int mid = (L2 + R2) / 2; 17 if (a[mid] <= x) L2 = mid + 1; 18 else R2 = mid; 19 } 20 cout << L2 - L; // x 出现的次数 = 上界 - 下界 21 return 0; 22}
单选题:程序输出是?
考点:二分统计出现次数(O2)。
解析:lower_bound(5) = 3(第一个 ),upper_bound(5) = 5(第一个 的位置),相减得出现次数 2。正确答案 A。
实现要点:有序数组数次数 = upper - lower,两次二分 ;无序数组才需要 扫描(I3)。
排除法:B(3)是 lower_bound 的下标;C(5)是 upper_bound 的下标;D(8)是数组长度。
关联 · 手写 upper_bound(J7):两者之差即次数。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {2, 5, 8, 12, 18}; // 升序数组 05 int x = 10; 06 int l = 0, r = 4, ans = a[0]; 07 while (l <= r) { 08 int mid = (l + r) / 2; 09 if (abs(a[mid] - x) < abs(ans - x)) ans = a[mid]; // 更近就更新 10 else if (abs(a[mid] - x) == abs(ans - x)) ans = min(ans, a[mid]); // 并列取较小值 11 if (a[mid] < x) l = mid + 1; 12 else r = mid - 1; 13 } 14 cout << ans; // 与 x 最接近的数 15 return 0; 16}
单选题:程序输出是?
考点:查找最接近的数(O3)。
解析:ans 初值 (距离 ):mid=2 得 (距离 )→ ans=8;mid=3 得 (距离 ,与 并列)→ min(8, 12) = 8。输出 8。正确答案 A。
实现要点:找最接近值 = 二分过程中用"距离"更新 ans:更近则换,距离相等取较小值。注意"并列取较小"是题目规则,换题可能取较大。
排除法:B(12)是并列时取大的结果;C(2)是初始值(没被更新到最优);D(10)是目标值本身,不是数组元素。
关联 · 二分找插入位置(K6):同款"位置推导"的邻近应用。
01#include <bits/stdc++.h> 02using namespace std; 03int p[3] = {1, 4, 7}, L = 10; // 已有 3 盏路灯在 1/4/7,路长 10(起点 0) 04int need(int mid) { // 要求相邻灯间距 <= mid,还需补几盏灯 05 int cnt = 0, last = 0; // last:上一盏灯的位置(起点 0 视为有灯) 06 for (int i = 0; i < 3; i++) { 07 while (p[i] - last > mid) { cnt++; last += mid; } // 缺口处每隔 mid 补一盏 08 last = p[i]; 09 } 10 while (L - last > mid) { cnt++; last += mid; } // 终点段同理 11 return cnt; 12} 13int main() { 14 cout << need(2); 15 return 0; 16}
单选题:程序输出是?
考点:路灯二分答案模拟(O4)。
解析:要求相邻灯间距 :灯 ( 不用补)→ 灯 (缺口 补 1 盏)→ 灯 (缺口 补 1 盏)→ 终点( 补 1 盏),共补 3 盏。正确答案 A。
实现要点:check(mid) 内模拟 = last 记上一盏灯,缺口每超 mid 就 cnt++ 并 last += mid(相当于每隔 mid 放一盏)。这是"二分答案 + 模拟验证"的模板。
排除法:B(2)漏数终点段;C(5)按"总长/间距"整除 的误解;D(0)把"已有 3 盏"当满足。
关联 · check 内部的贪心验证(E6):模拟/贪心验证是 check 的两大形态。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[7] = {10, 20, 30, 40, 50, 60, 70}; // 升序数组 05 int x = 40; 06 int l = 0, r = 6; 07 while (l <= r) { 08 int mid = (l + r) / 2; 09 if (a[mid] == x) { cout << a[mid] - mid; return 0; } // 输出的是值减下标 10 else if (a[mid] < x) l = mid + 1; 11 else r = mid - 1; 12 } 13 return 0; 14}
单选题:程序输出是?
考点:下标与值的辨析(O5)。
解析: 在下标 :命中时输出 a[mid] - mid = 40 - 3 = 37。正确答案 A。
实现要点:二分代码里 mid 是下标、a[mid] 是值。题目故意输出"值减下标"——读代码题先分清每个变量是位置还是数值。
排除法:B(3)是下标;C(40)是值;D(43)把减号算成加号。
关联 · 顺序查找输出下标(I1):
i与a[i]的辨析贯穿所有查找题。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {1, 3, 5, 7, 9}; 05 int x = 3; 06 int l = 0, r = 4; 07 while (l < r) { 08 int mid = (l + r) / 2; 09 if (a[mid] <= x) l = mid; // 错误:应该写 l = mid + 1 10 else r = mid - 1; 11 } 12 cout << l; 13 return 0; 14}
单选题:程序运行结果是?
考点:死循环的成因(P1)。
解析:l = 0, r = 4:mid=2 得 ;mid=0 得 ——l 没变,区间 [0, 1] 卡死,无限循环。正确答案 A。
实现要点:l = mid(不排除)必须配上取整中点 (l + r + 1) / 2;本代码用向下取整 (l + r) / 2,当 l = r - 1 时 mid == l,l 永不前进。修复:l = mid + 1 或换中点。
排除法:B(1)是修复后才会输出的答案;C(0)是 l 卡住的值但循环不退出;D 错——这是二分最经典的死循环。
关联 · 死循环:l 与 mid 的关系(H1):概念题的代码实证。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int l = 1500000000, r = 1600000000; // 都接近 int 上限 05 int mid = (l + r) / 2; // l + r 溢出后再除以 2 06 cout << mid; 07 return 0; 08}
单选题:在常见 32 位 int(补码截断)环境下,程序输出是?
考点:中点溢出的结果(P2)。
解析:l + r = 3100000000 超出 int(),补码截断为 ,/2 得 -597483648。正确答案 B。
实现要点:溢出发生在除法之前。验算方法:。修复:l + (r - l) / 2,或 long long。
排除法:A(1550000000)是数学正确值,但本题环境是 int 截断;C——编译器允许,运行时才溢出;D 无依据。
关联 · 中点加法的溢出(H2):与 K1 同题双现,强化记忆。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {2, 4, 6, 8, 10, 12}; 05 int x = 13; // 数组中不存在 13 06 int l = 0, r = 6; // 左闭右开 [0, 6) 的 r 07 while (l <= r) { // 错误:左闭右开却用了 <= 08 int mid = (l + r) / 2; 09 if (a[mid] == x) { cout << mid; return 0; } 10 else if (a[mid] < x) l = mid + 1; 11 else r = mid - 1; 12 } 13 cout << -1; 14 return 0; 15}
单选题:程序最可能出现什么情况?
考点:区间开闭不统一(P3)。
解析:r = 6 是左闭右开 [0, 6) 的约定,循环却用 <=:查 时 l 升到 6,下一轮访问 a[6]——数组只有 ,越界(行为未定义)。正确答案 A。
实现要点:区间开闭必须全代码统一:左闭右开 → r = n + while (l < r) + r = mid;闭区间 → r = n - 1 + while (l <= r) + r = mid - 1。混搭 = 越界或漏查。
排除法:B(输出 -1)是正确写法才会有的结果;C(6)是越界下标被误当答案;D(5)是最后一个合法下标。
关联 · 闭区间的循环条件(C2):两套区间约定要配套。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {2, 4, 6, 8, 10}; 05 int x = 7, l = 0, r = 4, mid; 06 while (l < r) { 07 mid = (l + r + 1) / 2; 08 if (a[mid] <= x) l = mid; // 找最后一个 <= 7 的下标 09 else r = mid - 1; 10 } 11 cout << mid; // 错误:输出了最后一次计算的 mid,而非答案 l 12 return 0; 13}
单选题:程序输出是?
考点:循环外误用 mid(P4)。
解析:循环结束时 l = 2(正确答案,最后一个 的是 a[2]=6),但代码输出的是最后一次计算的 mid = 3。正确答案 A。
实现要点:二分答案的出口是 l(或 r,两者相等),不是 mid——mid 只是过程中的探针。while (l < r) 框架下循环外 l == r 才是答案。
排除法:B(2)是正确答案,但代码输出的是 mid;C(6)是 a[2] 的值;D(8)是 a[3] 的值。
关联 · 上取整中点找最后一个(K3):正确写法输出
l,对照本题的错误写法。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int h[3] = {10, 8, 6}; // 3 棵树的高度 05 int M = 6; // 至少需要 6 米木材 06 long long wood(int H) { 07 long long s = 0; 08 for (int i = 0; i < 3; i++) 09 if (h[i] > H) s += h[i] - H; 10 return s; 11 } 12 int l = 0, r = 1000000000; 13 while (l < r) { 14 int mid = (l + r + 1) / 2; 15 if (wood(mid) <= M) l = mid; // 错误:判定方向写反 16 else r = mid - 1; 17 } 18 cout << l; 19 return 0; 20}
单选题:程序输出是?
考点:check 方向写反(P5)。
解析:条件写成 wood(mid) <= M(方向反): 越大 wood 越小,""越来越容易成立 → l 一路涨到 r,输出 1000000000。正确答案 A。
实现要点:二分答案求最大可行时,判定必须是"可行则 l = mid"。写反判定 = 把"可行域"和"不可行域"互换,答案冲到边界。修复:wood(mid) >= M。
排除法:B(6)是正确判定的答案;C(0)是下界;D(5)可行非最大。
关联 · check 方向写反(H5):概念题的代码实证。