题目链接
题目描述
给定一个 大小的矩阵,矩阵第
行第
列的元素为
。小红想知道矩阵中第
小的元素是多少。
解题思路
这是一个在概念矩阵(Conceptual Matrix)中寻找第 小元素的问题。
1. 暴力解法的局限性
由于 和
的范围可达
,矩阵的大小可能达到
。直接构建这个矩阵并排序是不可行的,会超出时间和内存的限制。
2. 二分答案
这类“寻找第 小/大”的问题,通常是二分答案的经典应用场景。我们可以不直接寻找这个数本身,而是去猜测这个数可能是多少。
-
单调性: 假设我们猜测答案是
。我们可以设计一个函数
count_le(x)
,用来计算矩阵中有多少个元素小于或等于。 这个
count_le(x)
函数显然具有单调非递减的性质:随着的增大,小于等于
的元素个数只会增加或不变。
我们的目标是找到一个最小的
,使得
count_le(x)
的值大于或等于。这个
就是第
小的元素。
-
check(x)
函数的设计: 二分答案的核心在于高效地实现check
函数,即count_le(x)
。 我们需要计算满足的数对
的数量,其中
且
。
我们可以遍历每一行
(从
到
):
- 对于固定的第
行,我们要找的是有多少个
满足
,即
。
- 同时,
的取值范围是
。
- 因此,对于第
行,满足条件的
的个数为
。
我们将每一行的这个计数值累加起来,就得到了整个矩阵中不大于
的元素总数:
这个
check
函数的时间复杂度为。
- 对于固定的第
-
二分过程:
- 我们可以在值域
内对答案进行二分查找。
- 取中点
。如果
check(mid)
计算出的计数值小于,说明
太小了,第
小的数一定比
大,所以我们去右半区间
查找。
- 如果计数值大于或等于
,说明
可能就是答案,或者答案更小,所以我们去左半区间
查找。
- 我们可以在值域
代码
#include <iostream>
#include <algorithm>
using namespace std;
// 计算矩阵中有多少个元素小于等于 x
long long count_le(long long x, long long n, long long m) {
long long count = 0;
for (long long i = 1; i <= n; ++i) {
count += min(m, x / i);
}
return count;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
long long n, m, k;
cin >> n >> m >> k;
long long left = 1, right = n * m;
long long ans = n * m;
while (left <= right) {
long long mid = left + (right - left) / 2;
if (count_le(mid, n, m) >= k) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
cout << ans << endl;
return 0;
}
import java.util.Scanner;
public class Main {
// 计算矩阵中有多少个元素小于等于 x
private static long countLe(long x, long n, long m) {
long count = 0;
for (long i = 1; i <= n; i++) {
count += Math.min(m, x / i);
}
return count;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
long m = sc.nextLong();
long k = sc.nextLong();
long left = 1, right = n * m;
long ans = n * m;
while (left <= right) {
long mid = left + (right - left) / 2;
if (countLe(mid, n, m) >= k) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
System.out.println(ans);
}
}
def count_le(x, n, m):
"""计算矩阵中有多少个元素小于等于 x"""
count = 0
for i in range(1, n + 1):
count += min(m, x // i)
return count
def main():
n, m, k = map(int, input().split())
left, right = 1, n * m
ans = n * m
while left <= right:
mid = (left + right) // 2
if count_le(mid, n, m) >= k:
ans = mid
right = mid - 1
else:
left = mid + 1
print(ans)
if __name__ == "__main__":
main()
算法及复杂度
- 算法:二分答案。
- 时间复杂度:
。二分查找的值域范围是
,需要
次迭代。在每次迭代中,
check
函数需要的时间来遍历每一行并计算计数值。
- 空间复杂度:
。我们没有存储整个矩阵,只需要常数级别的额外空间。