存在某条件使得一边均满足,一边均不满足:
如果问题满足某种条件,使得在某个点之前的所有值都满足条件,而之后的所有值都不满足条件(或反之),那么可以使用二分法来找到这个边界。
1.问题的解具有单调性
这是使用二分法的核心条件。单调性可以理解为:
- 如果某个值 x 满足条件,那么所有小于 x 的值也必然满足条件。
- 如果某个值 x 不满足条件,那么所有大于 x 的值也必然不满足条件。
2.问题的典型问法
- 最大化在满足某条件下的解值:例如“求某个最大值的最小值”。
- 最小化在满足某条件下的解值:例如“求某个最小值的最大值”。
- 求某个变量的最大值或最小值:例如“求最小的 x 使得某个条件成立”
3.相关的题目练习
1.分巧克力
问题描述
儿童节那天有 KK 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 NN 块巧克力,其中第 ii 块是 Hi×WiHi×Wi 的方格组成的长方形。为了公平起见,
小明需要从这 NN 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:
-
形状是正方形,边长是整数;
-
大小相同;
例如一块 6×56×5 的巧克力可以切出 66 块 2×22×2 的巧克力或者 22 块 3×33×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入描述
第一行包含两个整数 N,KN,K (1≤N,K≤1051≤N,K≤105)。
以下 N 行每行包含两个整数 Hi,WiHi,Wi (1≤Hi,Wi≤1051≤Hi,Wi≤105)。
输入保证每位小朋友至少能获得一块 1x1 的巧克力。
输出描述
输出切出的正方形巧克力最大可能的边长。
输入输出样例
例示
输入
2 10
6 5
5 6
输出
2
问题背景
想象一下,你有很多块巧克力,每块巧克力的长和宽都不一样。现在你想要把这些巧克力切成边长为 x
的正方形小块,然后把这些小块拼成一个更大的正方形。问题是:最大的 x
是多少?(也就是说,x
越大越好,但必须保证所有巧克力切出来的小块总数至少有 k
个。)
思路讲解
-
什么是二分查找?
-
二分查找就像在玩“猜数字”的游戏。你心里想一个数字,我猜中间的数字,如果猜大了,你就说“小了”,我就只猜左边的;如果猜小了,你就说“大了”,我就只猜右边的。这样每次都能把范围缩小一半,很快就能找到答案。
-
-
如何用二分查找解决问题?
-
我们要找的
x
是一个范围,比如从1
到1000000
。我们用二分查找的方法,不断缩小这个范围,直到找到最大的x
。
-
-
如何判断一个
x
是否可行?-
这里有一个关键函数
check(x)
,它的作用是判断:如果用边长为x
的正方形切割所有巧克力块,能否切出至少k
个小块。 -
对于每块巧克力,能切出的小块数量是
(长 / x) * (宽 / x)
。比如,一块长 5、宽 3 的巧克力,如果x=2
,那么能切出(5/2)=2
行,(3/2)=1
列,总共2*1=2
个小块。 -
把所有巧克力切出来的小块数量加起来,如果总数大于等于
k
,说明这个x
是可行的。
-
-
二分查找的步骤
-
初始化左右边界:
l=1
(最小可能的边长),r=1000000
(最大可能的边长)。 -
每次取中间值
mid
,看看这个mid
是否可行:-
如果可行,说明可以尝试更大的
x
,所以把左边界l
更新为mid
。 -
如果不可行,说明需要更小的
x
,所以把右边界r
更新为mid-1
。
-
-
重复这个过程,直到范围缩小到一个值,这个值就是答案。
-
import java.util.Scanner;public class Main {static int n = 0; // 巧克力块的数量static int k = 0; // 需要切割出的正方形总数static int ans = 0; // 最终答案static final int N = 100020; // 定义数组的最大长度static int[][] a = new int[N][3]; // 存储每个巧克力块的长和宽// 创建判断函数 check,用于判断当前的 x 是否满足条件public static boolean check(int x) {int count = 0; // 记录切割出的正方形总数// 遍历每个巧克力块for (int i = 1; i <= n; i++) {// 每个巧克力块切割出的正方形数量为 (长 / x) * (宽 / x)count += (a[i][1] / x) * (a[i][2] / x);}// 如果总数大于等于 k,说明当前 x 是可行的if (count >= k) {return true;} else {return false;}}public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 输入巧克力块的数量和需要切割出的正方形总数n = sc.nextInt();k = sc.nextInt();// 读取每个巧克力块的长和宽for (int i = 1; i <= n; i++) {a[i][1] = sc.nextInt(); // 长a[i][2] = sc.nextInt(); // 宽}// 初始化二分查找的左右边界int l = 1; // 最小可能的边长int r = 1000000; // 最大可能的边长// 二分查找循环while (r > l) {int mid = (r + l + 1) >> 1; // 计算中间值(右移一位相当于除以 2)// 判断当前 mid 是否满足条件if (check(mid)) {// 如果满足条件,尝试更大的 xl = mid;} else {// 如果不满足条件,尝试更小的 xr = mid - 1;}}// 循环结束时,l 即为满足条件的最大 xans = l;System.out.println(ans); // 输出答案}
}
2.卡牌
问题描述
这天, 小明在整理他的卡牌。
他一共有 nn 种卡牌, 第 ii 种卡牌上印有正整数数 i(i∈[1,n])i(i∈[1,n]), 且第 ii 种卡牌 现有 aiai 张。
而如果有 nn 张卡牌, 其中每种卡牌各一张, 那么这 nn 张卡牌可以被称为一 套牌。小明为了凑出尽可能多套牌, 拿出了 mm 张空白牌, 他可以在上面写上数 ii, 将其当做第 ii 种牌来凑出套牌。然而小明觉得手写的牌不太美观, 决定第 ii 种牌最多手写 bibi 张。
请问小明最多能凑出多少套牌?
输入格式
输入共 3 行, 第一行为两个正整数 n,mn,m 。
第二行为 nn 个正整数 a1,a2,…,ana1,a2,…,an 。
第三行为 nn 个正整数 b1,b2,…,bnb1,b2,…,bn 。
输出格式
一行, 一个整数表示答案。
样例输入
4 5
1 2 3 4
5 5 5 5
样例输出
3
样例说明
这 5 张空白牌中, 拿 2 张写 1 , 拿 1 张写 2 , 这样每种牌的牌数就变为了 3,3,3,43,3,3,4, 可以凑出 3 套牌, 剩下 2 张空白牌不能再帮助小明凑出一套
二分法题型的做题思路
1. 接受数值
首先,我们需要明确题目要求的输入数据是什么。通常,二分法问题会涉及一些数组或数值,我们需要读取这些数据。
2. 定义数组
根据题目需求,定义存储数据的数组或其他数据结构。例如,存储卡牌数量、空白牌数量等。
3. 判断情况
分析题目,确定二分查找的适用场景。二分法通常适用于以下情况:
-
单调性:问题具有某种单调性,例如数组是有序的,或者某个条件满足时所有左边的值都满足,右边的都不满足。
-
边界问题:需要找到某个边界值,例如最大值、最小值、第一个满足条件的值等。
4. 定义左右边界
根据问题的范围,初始化二分查找的左右边界:
-
左边界:通常是问题的最小可能值。
-
右边界:通常是问题的最大可能值。
5. 创建 check
函数
check
函数是二分法的核心,用于判断某个中间值是否满足条件。这个函数需要根据具体问题来设计。
import java.util.Scanner;public class Main {static long n = 0; // 卡牌种类数static long m = 0; // 空白牌数量static final int N = 10001000; // 定义数组的最大长度static int[] a = new int[N]; // 存储每种卡牌的现有数量static int[] sum = new int[N]; // 存储每种卡牌最多可以补充的空白牌数量// 判断函数,用于判断是否可以凑出 x 套牌public static boolean check(int x) {long v = m; // 初始化可用的空白牌数量for (int i = 1; i <= n; i++) {// 如果现有卡牌数量大于等于 x,说明这种卡牌足够,不需要补充if (a[i] >= x) {continue;}// 如果现有卡牌加上补充的空白牌仍然小于 x,说明无法凑出 x 套牌if (a[i] + sum[i] < x) {return false;}// 计算需要补充的空白牌数量long needed = x - a[i];// 如果可用的空白牌数量不足以补充,返回 falseif (v < needed) {return false;}// 减去已使用的空白牌数量v -= needed;}return true; // 如果所有卡牌都满足条件,返回 true}public static void main(String[] args) {Scanner scan = new Scanner(System.in);// 读取输入数据n = scan.nextLong(); // 卡牌种类数m = scan.nextLong(); // 空白牌数量for (int i = 1; i <= n; i++) {a[i] = scan.nextInt(); // 每种卡牌的现有数量}for (int i = 1; i <= n; i++) {sum[i] = scan.nextInt(); // 每种卡牌最多可以补充的空白牌数量}// 初始化二分查找的左右边界int l = 1; // 最小可能的套牌数int r = 3 * N; // 最大可能的套牌数(一个很大的数)// 二分查找循环while (l < r) {// 计算中间值int mid = (l + r + 1) >> 1;// 判断中间值是否可行if (check(mid)) {// 如果可行,尝试更大的值l = mid;} else {// 如果不可行,尝试更小的值r = mid - 1;}}// 输出结果System.out.println(l); // 最大可以凑出的套牌数scan.close();}
}