存在某条件使得一边均满足,一边均不满足:
如果问题满足某种条件,使得在某个点之前的所有值都满足条件,而之后的所有值都不满足条件(或反之),那么可以使用二分法来找到这个边界。
这是使用二分法的核心条件。单调性可以理解为:
问题描述
儿童节那天有 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)) {
// 如果满足条件,尝试更大的 x
l = mid;
} else {
// 如果不满足条件,尝试更小的 x
r = mid - 1;
}
}
// 循环结束时,l 即为满足条件的最大 x
ans = l;
System.out.println(ans); // 输出答案
}
}
问题描述
这天, 小明在整理他的卡牌。
他一共有 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];
// 如果可用的空白牌数量不足以补充,返回 false
if (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();
}
}