1. 问题描述:
儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。 小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。 为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:
1. 形状是正方形,边长是整数
2. 大小相同
例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?
输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。
输出
输出切出的正方形巧克力最大可能的边长。
样例输入
2 10
6 5
5 6
样例输出
2
来源:http://oj.ecustacm.cn/problem.php?id=1323
2. 思路分析:
① 分析题目可以知道这K块巧克力都是正方形而且大小都是一样的,我们希望找到边长尽可能大的巧克力使得切出来的个数满足K个小朋友的需求。题目其实已知最大的矩形边长为100000,最小边长为1我们的目标是在这个范围内找到一个边长最大为t的正方形,使得切出来的个数刚好是K,分析到这里应该可以想到二分查找了吧。二分查找解决的题目一般是已知一个范围,求解的问题是一般是从这个范围中找出满足题目要求的最佳值,知道使用二分查找那么剩下来的就比较好办了,都是二分查找的套路,有的时候直接套用模板就行。
② 二分查找一般都是可以使用模板解决的,首先在for循环中求解出左右边界[l, r]的中间范围,通过一个check函数传递这个中间范围结合题目的要求判断当前的mid是否满足题目的条件,如果满足那么就更新左边界或者是右边界,在check函数中满足的条件下可以使用一个变量res来记录满足题目要求的中间值(使用变量记录的目的是为了避免最后左右边界上的处理导致的错误),最后根据题目的要求输出或者返回res变量值即可。对于这道题目来说我们需要在最小范围1与最大范围100000中找到一个最大的边长mid,使用check函数判断当前的边长mid是否满足切出来的数目大于等于k的条件,其实计算每一个矩形能够切出来的正方形的数目利用画图就很好理解了(下面是6 * 5的矩形最多可以切6个2 * 2的正方形),将当前矩形的边长分别除以mid之后相乘的结果就是当前能够切割的边长为mid的正方形的个数,判断切出的个数是否大于等于K来确定mid是否满足题目的条件,根据这个mid来更新左右边界的范围。
3. 代码如下:
from typing import List
def check(m: int, k: int, rec: List[tuple]):
count = 0
for i in range(len(rec)):
count += (rec[i][0] // m) * (rec[i][1] // m)
if count >= k: return True
return False
if __name__ == '__main__':
# 这一题使用二分查找的方法解决可以减小时间复杂度
n, k = map(int, input().split())
rec = list()
for i in range(n):
h, w = map(int, input().split())
# 使用元组封装起来比较方便, 元组一般可以封装多个属性在一定程度下相当于是java中的一个类了非常方便
rec.append((h, w))
l, r, res = 0, 100000, 0
while l <= r:
mid = (l + r) // 2
if check(mid, k, rec):
res = mid
l = mid + 1
else:
r = mid - 1
print(res)