1. 问题描述:
给定一个 n × m 的方格矩阵,每个方格内都有一个整数元素。其中第 i 行第 j 列的方格中的元素为 i×j(行和列都从 1 开始编号)。现在,需要你将这 n × m 个整数按照非严格单调递增的顺序一一写出。请问,你写出的第 k 个整数是多少。
输入格式
一行,三个整数 n,m,k。
输出格式
一行,输出你写出的第 k 个整数。
数据范围
前 6 个测试点满足 1 ≤ n,m ≤ 10。
所有测试点满足 1 ≤ n,m ≤ 5 × 10 ^ 5,1 ≤ k ≤ n × m。
输入样例1:
2 2 2
输出样例1:
2
输入样例2:
2 3 4
输出样例2:
3
输入样例3:
1 10 5
输出样例3:
5
来源:https://www.acwing.com/problem/content/4083/
2. 思路分析:
首先我们需要分析一下如果x是答案那么它需要满足什么样的性质,可以发现当我们将矩阵中的元素排序之后有可能多个数字是相同的,也即出现连续一段相等的数字,x左边的数字的个数一定小于k,x右边到n * m之间的数字的个数一定大于等于k个,也即x的左边是不满足要求的,x的右边是满足要求的,所以x是二段性的分界点,这样我们就可以通过二分来枚举答案。
我们需要确定一下二分枚举的细节,我们可以从1~n * m枚举,其中1~n * m是不重复的数字,相当于是一个数轴,判断当前枚举的mid是否满足条件,计算矩阵中小于等于mid的数的个数是否大于等于k,如果大于等于k说明答案在mid或者是mid的左边,下一次枚举的区间为[l,mid],否则在mid的右边,也即[mid + 1,r],所以问题就转化为如何快速计算矩阵中小于等于mid的个数,对于当前的第i行,i * j <= mid,那么j <= mid / i,每一行小于等于mid的数的个数是可以直接计算出来的,所以我们可以枚举所有的行,可以使用O(n)的时间计算出矩阵中小于等于mid的数的个数,最后判断是否大于等于k即可,通过check函数来判断是否满足条件更新下一次枚举的区间即可。
3. 代码如下:
class Solution:
# 判断小于等于mid的数的个数是否大于等于k
def check(self, mid: int, n: int, m: int, k: int):
res = 0
for i in range(1, n + 1):
# 注意mid // i除以i的结果可能大于m, 所以需要与m取一个min, mid / i表示当前这一行小于等于mid的数的个数
res += min(m, mid // i)
return res >= k
def process(self):
n, m, k = map(int, input().split())
l, r = 1, n * m
while l < r:
mid = l + r >> 1
# 小于等于mid的数字大于等于k说明答案在mid的左边
if self.check(mid, n, m, k):
r = mid
else:
l = mid + 1
return r
if __name__ == "__main__":
print(Solution().process())