【每日一题】Problem 414B. Mashmokh and ACM
解决思路
- 先计算 \([1, n]\) 中的约数集合
- \(dp[i][j](i\in [1, n], j\in [1, k])\) 表示第 \(j\) 个位置放置 \(i\) 所拥有的可能性,即 \(i\) 的约数集合的可能性总和
- 以此类推,到达 \(k\) 时,计算 \(\sum_{i=1}^{n}dp[i][k]\) 即可
#include <bits/stdc++.h>
int main() {
int n, k; std::cin >> n >> k;
std::map<int, std::vector<int>> m;
m[0] = std::vector<int>(n + 1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
if (i % j == 0) {
if (m.find(i) == m.end()) m[i] = std::vector<int>();
m[i].push_back(j);
}
}
m[0][i] = i;
}
long long ans = 0;
std::vector<std::vector<long long>> dp(
n + 1, std::vector<long long>(k + 1, 0));
// 由于约数不可能超过自己,因此以按序计算出 i 在 j 位置的可能性的顺序是可以的
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= k; ++j) {
if (j == 1) dp[i][j] = 1;
else {
// 遍历约数集合
for (auto &v : m[i]) {
dp[i][j] += dp[v][j - 1];
dp[i][j] %= 1000000007;
}
}
if (j == k) {
ans += dp[i][j];
ans %= 1000000007;
}
}
}
std::cout << ans << std::endl;
return 0;
}
更好的解
上面的方法中,由于使用 \(O(n^2)\) 的复杂度计算约数,以及频繁地 push_back,导致比较耗时
看题解时看到优化后的思路
// 遍历可以整除 j 的 m
for (int m = j; m <= n; m += j) {
dp[i][m] = (dp[i][m] + dp[i - 1][j]) % MODULO;
}
更新后的代码如下
#include <bits/stdc++.h>
#define MODULO 1000000007;
int main() {
int n, k; std::cin >> n >> k;
long long ans = 0;
std::vector<std::vector<int>> dp(
k + 1, std::vector<int>(n + 1, 0));
for (int i = 1; i <= n; ++i) dp[1][i] = 1;
for (int i = 1; i <= k; ++i) {
for (int j = 1; j <= n; ++j) {
// 遍历可以整除 j 的 m
for (int m = j; m <= n; m += j) {
dp[i][m] = (dp[i][m] + dp[i - 1][j]) % MODULO;
}
if (i == k) {
ans += dp[i][j];
ans %= MODULO;
}
}
}
std::cout << ans << std::endl;
return 0;
}
cache 命中
方法二中替换了方法一的 \(i, j\) 的顺序,但是将方法二修改为方法一的方式仍然是可以的,因为约数不会超过自己
但是需要注意的是,如果修改为方法一的顺序,会影响 cache 命中,因为需要频繁地修改行而不是列
本文来自博客园,作者:HelloEricy,转载请注明原文链接:https://www.cnblogs.com/HelloEricy/p/17521220.html