题目链接

小红的矩阵

题目描述

给定一个 大小的矩阵,矩阵第 行第 列的元素为 。小红想知道矩阵中第 小的元素是多少。

解题思路

这是一个在概念矩阵(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 函数需要 的时间来遍历每一行并计算计数值。
  • 空间复杂度。我们没有存储整个矩阵,只需要常数级别的额外空间。