二分(蓝桥杯备赛必看)

基础知识:

二分法:每次将搜索范围缩小一半,可以在O(logn)时间内找到正确答案。
二分法的前提条件:单调性

核心:

  • 利用单调性调整二分查找的区间
  • 根据中点值来调整区间,减少一半的搜索范围

例如:手算根号值

步骤:

  • 1、候选区间[left,right]
  • 2、不断循环,直至区间满足特定条件
  • 2.1、计算中点mid=(left+right)/2
  • 2.2、判断中点是否合法,根据中点的计算结果调整[left,right]

用途:

  1. 查找有序列表a中元素x的下标:见语法部分的二分:bisect---数组二分查找算法
  2. 浮点二分
  3. 二分答案

近似根号代码:

left, right = 1, 2
#[1.0001,1.0002]
eps = 1e-4
while (right-left) >= eps:
    mid = (left + right) / 2
    if mid * mid > 2:
        #[left,mid]
        right = mid
    else:
        #[mid,right]
        left = mid

print(left,right)
left, right = 1, 2
for _ in range(50):
    mid = (left + right) / 2
    if mid * mid > 2:
        #[left,mid]
        right = mid
    else:
        #[mid,right]
        left = mid
print(left,right)

二分答案

题目所求答案(一般为整数)具有单调性质,采用猜答案+二分
1、确定初始范围[left,right]
2、当left<right时: mid = (left + right)//2
check(mid):判断mid是否合法:(定义check函数,什么时候是合法,根据题目的单调性来确定)
如果合法:更新ans=mid
根据合法调整左右区间,调整策略为二选一:

  • left=mid+1
  • right=mid-1

最大值最小化和最小值最大化经常用到。

如果你要求的答案是最小值,你要看看左边是不是还有更小的合法值,反之就要去看看右边。

代码模板

#二分答案
def check(x):
    #判断x是否合法,合法返回True,否则返回False
    pass
left,right,ans = 初始化
while left<=right:
    mid=(left+right)//2
    if check(mid):
        ans= mid
        left = mid + 1 #二选一
    else:
        right= mid - 1 #二选一
print(ans)

例题

蓝桥99 分巧克力

import os
import sys

# 请在此输入您的代码
#边长越大,块数越少,边长越小,块数越多。
def check(x):
  # 假设x边长合法
  # 合法条件:边长为x能够切出k块
  cnt = 0 # 能够切出的块数
  for H, W in a:
    cnt += (H//x) * (W//x)
  return cnt >= K # 如果合法,返回True,否则,返回False
N, K = map(int, input().split())
a = []
for _ in range(N):
  h, w = map(int, input().split())
  a.append((h, w))

left, right, ans = 1, 100000, 1 # 可能的最小值,最大值,根据题意得,或者是数据范围。
while left<=right:              # 在这个范围内猜答案
    mid=(left + right) // 2
    if check(mid):
        ans= mid
        left = mid + 1 #二选一
    else:
        right= mid - 1 #二选一 根据题意选
print(ans)

蓝桥364 跳石头

import os
import sys

# 请在此输入您的代码
def check(x):
  # 移除的石头越多,最短的跳跃距离会变大
  # 假设最短跳跃距离为x,移除石头不超过M即为合法
  cnt = 0 # 表示移除的数量
  last_idx = 0 # 表示上一个位置
  for i in range(N):
    if a[i] - last_idx >= x: 
      last_idx = a[i]
    else:
      cnt += 1 # 当两个石头之间的距离小于最小距离,要移除石头
  if L - last_idx < x: # 最后一块石头的特判,因为最后一块不能移
    return False
  return cnt <= M
L, N, M = map(int, input().split())
a = []
for i in range(N):
  a.append(int(input()))

left, right = 1, L
ans = -1
while left <= right:   # 经典模板
  mid = (left + right) // 2
  if check(mid):
    ans = mid
    left = mid + 1 # 最小距离的最大值,说明要从mid右边找,是不是还有更大的合法值。
  else:
    right = mid - 1
print(ans)

蓝桥3404 肖恩的乘法表(第k大问题)

n, m, k = map(int, input().split())

def check(x):
    cnt = 0
    for i in range(1, n + 1):
        # i * j >= x
        cnt += min(x // i, m) # j 有限制,要在两者之中去最小值
    return cnt

left, right = 1, n * m
ans = 0

while left <= right:
    mid = (left + right) // 2
    if check(mid) >= k: # 前面至少有k个数字比x元素小的 比如1 2 3 3 4 其实 3是第3大的,但是有重复值。
        ans = mid
        right = mid - 1 # 找最小值,在mid左区间找
    else:
        left = mid + 1

print(ans)

总结:

  • 这题能不能用二分?首先,题目的变量要具有明显的单调性。
  • 然后应该依据题目判断什么是合法条件。
  • 判断完题目合法条件之后关注最终答案想要的是什么,是最小值?最大值?
  • 写好合法判断函数后,利用模板猜答案,注意特殊情况特判。
  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值