题解:洛谷 P10263 公倍数问题
【题目来源】
洛谷:P10263 [GESP202403 八级] 公倍数问题 - 洛谷
【题目描述】
小 A 写了一个 \(N \times M\) 的矩阵 \(A\),我们看不到这个矩阵,但我们可以知道,其中第 \(i\) 行第 \(j\) 列的元素 \(A_{i,j}\) 是 \(i\) 和 \(j\) 的公倍数(\(i=1,\dots,N\),\(j=1,\dots,M\))。现在有 \(K\) 个小朋友,其中第 \(k\) 个小朋友想知道,矩阵 \(A\) 中最多有多少个元素可以是 \(k\)(\(k=1,2,\dots,K\))。请你帮助这些小朋友求解。
注意:每位小朋友的答案互不相关,例如,有些位置既可能是 \(x\),又可能是 \(y\),则它同时可以满足 \(x,y\) 两名小朋友的要求。
方便起见,你只需要输出 \(\sum_{k=1}^{K}{k \times \texttt{ans}_k}\) 即可,其中 \(\texttt{ans}_k\) 表示第 \(k\) 名小朋友感兴趣的答案。
【输入】
第一行三个正整数 \(N,M,K\)。
【输出】
输出一行,即 \(\sum_{k=1}^{K}{k \times \texttt{ans}_k}\)。
请注意,这个数可能很大,使用 C++ 语言的选手请酌情使用 long long 等数据类型存储答案。
【输入样例】
2 5 2
【输出样例】
9
【核心思想】
-
问题分析:给定 \(N \times M\) 矩阵,\(A_{i,j}\) 是 \(i\) 和 \(j\) 的公倍数。对每个 \(k \in [1, K]\),求矩阵中最多有多少个元素可以是 \(k\),即满足 \(k\) 是 \(i\) 和 \(j\) 的公倍数的位置 \((i,j)\) 的数量。最终输出 \(\sum_{k=1}^{K} k \times ans_k\)。这是一个因数计数 + 乘法原理问题,核心在于将"公倍数"条件转化为"因数"计数。
-
算法选择:
- 因数个数预处理:\(a[i]\) 表示 \([1, N]\) 中 \(i\) 的因数个数,\(b[i]\) 表示 \([1, M]\) 中 \(i\) 的因数个数
- 乘法原理:\(ans_k = a[k] \times b[k]\),即 \(i\) 是 \(k\) 的因数且 \(j\) 是 \(k\) 的因数的 \((i,j)\) 对数
-
关键步骤:
- 读入数据:读取 \(N, M, K\)
- 预处理因数个数(\(count\_divisor(n, divisor[])\)):
- 枚举 \(i\) 从 \(1\) 到 \(n\):
- 枚举 \(j\) 从 \(i\) 到 \(MAX\) 步长 \(i\):
- \(divisor[j] \leftarrow divisor[j] + 1\)(\(i\) 是 \(j\) 的因数)
- 枚举 \(j\) 从 \(i\) 到 \(MAX\) 步长 \(i\):
- 枚举 \(i\) 从 \(1\) 到 \(n\):
- 计算答案(\(i\) 从 \(1\) 到 \(K\)):
- \(ans \leftarrow ans + i \times a[i] \times b[i]\)
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(MAX \log MAX + K)\),其中 \(MAX = 10^6\),因数预处理近似 \(O(MAX \log MAX)\)
- 空间复杂度:\(O(MAX)\),因数计数数组
-
因数计数与乘法原理的核心思想:
- 公倍数的因数分解:\(k\) 是 \(i\) 和 \(j\) 的公倍数 \(\Leftrightarrow i | k\) 且 \(j | k\)。因此位置 \((i,j)\) 可以是 \(k\) 当且仅当 \(i\) 和 \(j\) 都是 \(k\) 的因数
- 乘法原理应用:\([1, N]\) 中 \(k\) 的因数有 \(a[k]\) 个,\([1, M]\) 中 \(k\) 的因数有 \(b[k]\) 个,每个因数对 \((i,j)\) 对应一个矩阵位置,因此 \(ans_k = a[k] \times b[k]\)
- 埃氏筛思想预处理:通过枚举因数 \(i\) 并标记其倍数,高效计算每个数的因数个数,时间复杂度 \(O(MAX \log MAX)\),远优于逐个数的 \(O(\sqrt{n})\) 分解
- 答案独立性:题目说明每位小朋友的答案互不相关,因此可以直接累加各 \(k\) 的贡献,无需考虑元素冲突
- 适用于因数统计、乘法原理组合计数类问题
【算法标签】
普及 #约数
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 定义int为long long类型
int n, m, k, ans; // n: 行数, m: 列数, k: 遍历范围, ans: 答案
int a[1000005], b[1000005]; // a: 存储1~1000000的因数个数(针对n), b: 存储1~1000000的因数个数(针对m)
// 预处理函数:计算1~1000000的因数个数
// divisor[]: 存储结果的数组
// n: 实际需要计算的最大值
void count_divisor(int n, int divisor[])
{
// 类似埃氏筛法计算每个数的因数个数
for (int i = 1; i <= n; i++) // i是可能的因数
{
// 将i的倍数都增加1,因为i是这些数的因数
for (int j = i; j <= 1000000; j += i)
{
divisor[j] += 1; // j的因数个数加1
}
}
}
signed main() // 因为#define int long long,所以用signed main
{
// 输入n, m, k
cin >> n >> m >> k;
// 预处理计算因数个数
// a[i]: 表示在1~n范围内,i的因数个数
// b[i]: 表示在1~m范围内,i的因数个数
count_divisor(n, a);
count_divisor(m, b);
// 计算答案
for (int i = 1; i <= k; i++) // 遍历1到k
{
// 计算公式:ans = Σ(i=1 to k) [i * a[i] * b[i]]
ans += i * a[i] * b[i];
// 调试输出
// cout << "ans " << ans << endl;
}
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
2 5 2
9
浙公网安备 33010602011771号