2 二分专题

简单的算法,也有复杂的一面~

二分查找

①朴素二分

  • 找一个精确对应的值
两种情况 整数 浮点数
while条件 L <= R R - L > 0.00001
精确到小数点后4
更新参数 更新公式 更新公式
mid (L + R) / 2
为了避免溢出👇
((long long) L + R) / 2
L + (R - L) / 2
(L + R) / 2
L mid + 1 mid
R mid - 1 mid
  • 浮点数情况放在②、③也成立

②⭐特殊二分

  • 找0、1的交界处的1
两种情况 000000111111 111111100000
while条件 L != R 或者 L < R
(不能是L <= R,不会有L > R的情况)
同左
更新参数 更新公式 更新公式
mid (L + R) / 2 (L + R + 1) / 2
(避免遇到1 0死循环)
L mid + 1 mid
(mid可能是答案)
R mid
(mid可能是答案)
mid - 1

③⭐⭐二分答案

  • 特殊二分+func(答案)
    • 根据答案才得到判断更新条件的值func(答案)
    • 判断更新条件的值一般隐含着有序的条件
      • 所以是否sort跟func求解方便与否有关,不是必须

HZOJ-380:大统领投票(sort)

图片

样例输入

3
123456799
123456789132456789123456789
11111111111111

样例输出

2
123456789132456789123456789
  • 思路
    • 票数很大,要作为字符串读入,使用string类
      • string类重载了>,自带字典序比较
    • 利用struct类,结合对应的编号
    • 利用sort
      • 先比较长度,strlen
      • 长度一致,则使用字典序
  • 代码
    • 图片
    • const node &a的&是传引用,可以删去,但要加const~
    • 必须判断长度,否则53比12345的字典序大
    • 索引从1开始,否则引入了(0, 0)

HZOJ-381:谁拿了最多奖学金(sort)

图片

样例输入

4
YaoLin 87 82 Y N 0
ChenRuiyi 88 78 N Y 1
LiXin 92 88 N N 0
ZhangQin 83 87 Y N 1

样例输出

ChenRuiyi
9000
28700
  • 思路

    • 奖金并列第一时,输出最早出现的
      • 所以要记录编号,sort是不稳定的
    • 注意奖金计算规则细节即可
  • 代码

  • 图片

HZOJ-386:吃瓜群众(①)

图片

样例输入

5 3
1 3 26 7 15
26 99 3

样例输出

3
0
2
  • 思路
    • 排序+朴素二分
    • 瓜的数量和编号需揉成struct
  • 代码
    • 图片
    • mid计算公式,防止溢出
      • = ((long long)l + r) / 2
      • = l + (r - l) / 2
    • 没找到就输出0,f初始为0

HZOJ-387:吃瓜群众升级版(②)

图片

样例输入

5 5
1 3 26 7 15
27 10 3 4 2

样例输出

0
5
2
4
2
  • 思路
    • 抽象为一堆0、一堆1,找第一个为1的情况
    • 考虑输出0的情况
  • 代码
    • 图片
    • 有两种处理输出0的情况
    • 000000011111111

Leetcode-278:第一个错误的版本(②)

图片

示例

给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。 
  • 思路
    • 用特殊二分查找做
    • 000000011111111
  • 代码
class Solution {
public:
    int firstBadVersion(int n) {
        int l = 1, r = n;  // 版本号从1开始
        while (l != r) {
            int mid = ((long long)l + r) / 2;  // 防止溢出方式一
            // int mid = l + (r - l) / 2;  // 防止溢出方式二
            if (isBadVersion(mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return l;
    }
};

HZOJ-390:原木切割(③)

图片

样例输入

3 8
6
15
22

样例输出

5
  • 思路
    • 左界→右界:1→max(Xi)
    • 111111110000000
    • ③ = ② + func(mid)
      • func(mid):根据mid求出可以切出的段数,长度mid是要求的答案
  • 代码
    • 图片
    • 右界需要确定一下,输入的时候确定

HZOJ-389:暴躁的程序猿(③)

图片

样例输入

5 3
1
2
8
4
9

样例输出

3
  • 思路
    • 距离的上下界:1→max(Xi)
      • 为了简单,直接取1和最大的工位编号
      • 如果要精细,可以取最小和最大的工位编号之差
    • 答案→判断依据:距离→可以安排的人数
    • 111111100000
  • 代码
    • 图片
    • 排序了才好根据距离算可安排人数
    • func()
      • 要记录上一个安排的人的位置last
      • 求可安排的人数s
      • 初始化:可安排人数s为1,上一个安排的人的位置last为num[0]

HZOJ-393:切绳子(小数③)

图片

样例输入

4 11
8.02
7.43
4.57
5.39

样例输出

2.00
  • 思路
    • 类似原木切割,但是数据是浮点数
      • 条件、更新公式不一样
    • 确定查找的左右边界、根据答案得判断依据
  • 代码

HZOJ-82:伐木

图片

样例输入

4 9
20 17 15 10

样例输出

14
  • 思路
    • 类似原木切割,但判断依据是切下来的木材是否满足需要的木材长度
    • 上下界:0→max()
    • 注意数据范围很大,相加有可能溢出,所以使用long long
      • long long要想好位置,如果想不好,可以索性全用
  • 代码
    • 图片
    • 想清楚答案与判断依据之间的关系

HZOJ-391:数列分段

图片

样例输入

5 3
4
2
4
5
1

样例输出

6
  • 思路
    • 最小的最大和
    • 0000000111111,找第一个1
      • 答案(段数和)mid小→判断依据(段数)s多,不满足条件,对应0
      • 答案(段数和)mid大→判断依据(段数)s少,算是满足条件,对应1
      • 因为找的是最小的最大和,考虑交界处
      • ❓❗如果是找最大的最大和,就会变成111110000情况
    • 左右界:max(Ai)→sum(Ai)
  • 代码
    • 图片
    • 图片
    • ⭐func函数好好琢磨!
    • 注意long long类型

HZOJ-394:跳石头

图片

样例输入

25 5 2 
2
11
14
17 
21

样例输出

4
  • 思路
    • 最短跳跃距离的最大值
    • 起点和终点隐含存在:min(D(i+1) - Di)→L(起点到终点的距离)
    • 111110000
      • 因为找的是最大值,越往右值越大,所以找最后一个1
  • 代码
    • 图片
    • 要记得存起点和终点
    • 对遍历到终点石头但不会移走终点石头的思考
      • 可以用移走前一块石头等效

HZOJ-392:丢瓶盖

图片

样例输入

5 3
1
2
3
4
5

样例输出

2
  • 思路
    • 与上述暴躁的程序猿一模一样!
  • 代码

HZOJ-395:复制书稿

图片

样例输入

9 3
1 2 3 4 5 6 7 8 9

样例输出

1 5
6 7
8 9
  • 思路
    • 每个人的抄书速度都一样,数字就对应时间
    • 必须连续,书不能拆分
    • 步骤
      • 第一步:求最大值,与上述的数列分段一模一样!
      • 第二步:每个人抄书的起止编号,存在数组里
        • 拿到最大值后,从后往前扫数列
          • 如果有多解,让前面的人少抄写
        • 稍有麻烦,考察代码功底
  • 代码

附加知识点

  • C++里的struct为类
    • 成员访问权限默认私有,而struct默认公有
  • sort <algorithm> 使用
int num[105];
sort(num, num + n, cmp); 
    • num:要排序的区间首地址;num + n:区间尾地址的下一地址;cmp:
    • ⭐默认是不稳定的
    • 对于int、string:可以直接比较大小,默认从小到大排序
    • 对于自定义结构
      • 重载小于号
      • 自己写排序方法cmp
bool cmp(node a, node b) {
  return a.x > b.x;  // 按照node里的x从大到小排序
}
  • 图片

思考点

Tips


课程速记