问题描述
儿童节那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N块巧克力,其中第 i块是 Hi×Wi 的方格组成的长方形。为了公平起见,
小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:
-
形状是正方形,边长是整数;
-
大小相同;
例如一块 6×5的巧克力可以切出 6 块 2×2的巧克力或者 2 块 3×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入描述
第一行包含两个整数 N,K (1≤N,K≤10⁵)。
以下 N 行每行包含两个整数 Hi,Wi(1≤Hi,Wi≤10⁵)。
输入保证每位小朋友至少能获得一块 1x1 的巧克力。
输出描述
输出切出的正方形巧克力最大可能的边长。
输入输出样例
示例
输入
2 10
6 5
5 6
输出
2
运行限制
- 最大运行时间:2s
- 最大运行内存: 256M
参考代码:
java
package Practice3;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
/*1.输入巧克力个数和人数*/
int N = scan.nextInt(); //巧克力个数
int K = scan.nextInt(); //人数
/*2.定义二维数组记录各个巧克力的长、宽->列代表各个巧克力个数,行代表巧克力长、宽*/
int[][] chocolate = new int[N][2];
/*3.输入巧克力长、宽*/
for (int i = 0; i < N; i++) {
//输入长添加到巧克力数组中
chocolate[i][0] = scan.nextInt();
//输入宽添加到巧克力数组中
chocolate[i][1] = scan.nextInt();
}
/*核心思路:由于要保证每个人都分到巧克力,那么巧克力的个数必须尽可能的多,每一块巧克力必须是正方形,
* 因此每一块巧克力的边越短越好,但还有一个要求是巧克力需要尽可能地大,
* 那么就需要试一试比当前边长还大的边长是否也符合要求,如果符合,就需要取较大的边
* ->巧克力边长必须是整数,那么边长最小为1,
* 巧克力边长最大为100000(此时是1块巧克力,1个人,巧克力长、宽最大都是100000)
* 显然,从1开始找边长的话效率很低,因此可以采用二分法进行判断
* ->如果当前处理的边得出最终可分得的人数小于目标人数,就说明取的边长较大,就需要用小的边长重新计算,反之用大的边长*/
/*4.定义变量记录最大边长*/
int result;
/*5.调用maxLen函数得出最大边长*/
result = maxLen(chocolate, K);
/*6.输出*/
System.out.println(result);
}
//定义方法用来得出巧克力最大可切成的边长
private static int maxLen(int[][] chocolate, int K) { //K是人数
//这里不需要索引,就是找边长
/*1.定义变量记录边长边界值、中间值*/
int low = 1;
int high = 100000;
int mid;
/*2.定义变量记录最终最大边长*/
int max = 0; //max作为最终返回值需要初始化,如果不初始化的话最终可能返回的是空值
/*3.定义变量记录当前分给多少人巧克力->人数可能会很大,用long防止溢出(long范围大,可隐式转换)*/
long number = 0;
/*4.开始查找,需要循环,循环条件low<=high(代表只要搜索区间不为空,就继续搜索,直到找到所有可能边长中的最大值),
如果循环条件为number < K && flag == false,这会导致在某些情况下提前终止搜索,
flag==false表示flag为false时才可能进行循环,如果flag为true了,表示找到了一个可行解,
但之后是否还有更优的可行解就无法继续查找了,因为flag为true就不会再循环了->可能会漏解*/
while (low<=high) {
/*5.每一次循环都是重新分,那么分给的人数需要清0*/
number = 0;
/*6.取中间边长*/
//mid = (low + high) / 2;也对,但是当low和high都很大时可能溢出
mid = low + (high - low) / 2; //这里有做差,不容易溢出,而且等价mid = (low + high) / 2;
/*7.得出分给的人数*/
for (int i = 0; i < chocolate.length; i++) {
number = number + (chocolate[i][0] / mid) * (chocolate[i][1] / mid);
}
/*8.判断*/
if (number < K) {
//意味着分的大了,取左范围->用小的边长
high = mid - 1;
} else if (number >= K) {
//意味着每个人都能分到巧克力了,先记录当前正确的边长
max = mid;
//再尝试更大的边长
low = mid + 1;
}
}
/*9.返回最大边长*/
return max;
}
}
上述代码可优化,如下:
java
package Practice3;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int K = scan.nextInt();
int[][] chocolate = new int[N][2];
int maxSide = 0; // 记录最大可能边长(二分上界)
for (int i = 0; i < N; i++) {
chocolate[i][0] = scan.nextInt();
chocolate[i][1] = scan.nextInt();
// 更新最大可能边长(巧克力能切出的最大正方形边长受限于最小边)
maxSide = Math.max(maxSide, Math.min(chocolate[i][0], chocolate[i][1]));
}
int result = maxLen(chocolate, K, maxSide); //result是最终得出的边长
System.out.println(result);
}
private static int maxLen(int[][] chocolate, int K, int maxPossible) {
int low = 1;
int high = maxPossible > 0 ? maxPossible : 100000; //high也是二分上界
int max = 0;
while (low <= high) {
int mid = low + (high - low) / 2; // 防止溢出
long number = 0;
// 计算当前边长能切出多少块
for (int i = 0; i < chocolate.length; i++) {
number += (long) (chocolate[i][0] / mid) * (chocolate[i][1] / mid);
// 提前结束,优化性能
if (number >= K) {
break;
}
}
if (number >= K) {
// 当前边长可行,记录并尝试更大的
max = mid;
low = mid + 1;
} else {
// 当前边长不可行,尝试更小的
high = mid - 1;
}
}
return max;
}
}