题解:学而思编程 倍数序列2
【题目来源】
学而思编程:倍数序列2
【题目描述】
要求找到一个正整数序列,满足序列中所有的数不超过 \(n\),序列长度为 \(m\),且除了第一个数外,所有的数都能被前一个数整除(即是前一个数的倍数)。
我们想要知道这样的序列有多少个,输出所有满足要求的序列的个数,取除以 \(10^9+7\) 的余数。
【输入】
第一行,两个正整数 \(n\),\(m\)。
【输出】
输出一个满足要求的序列的个数,取除以 \(10^9+7\) 的余数。
【输入样例】
4 3
【输出样例】
13
【核心思想】
-
问题分析:给定数值上限 \(n\) 和序列长度 \(m\),求满足以下条件的正整数序列数量:
- 序列中所有数不超过 \(n\)
- 序列长度为 \(m\)
- 除第一个数外,每个数都能被前一个数整除(即后数是前数的倍数)
这是一个线性动态规划问题,状态需要记录序列长度和最后一个数的值。
-
算法选择:
- 线性 DP(二维):\(dp[i][j]\) 表示长度为 \(i\)、最后一个数为 \(j\) 的合法序列数量
- 因数枚举:通过枚举 \(j\) 的所有因数来转移状态
- 取模运算:答案对 \(10^9+7\) 取模
-
关键步骤:
- 初始化:\(dp[1][j] = 1\)(\(1 \leq j \leq n\)),长度为 \(1\) 的序列有 \(n\) 种
- DP 递推(\(i\) 从 \(2\) 到 \(m\),\(j\) 从 \(1\) 到 \(n\)):
- 枚举 \(j\) 的所有因数 \(k\)(通过 \(k \times k \leq j\) 优化)
- 若 \(k\) 是 \(j\) 的因数:\(dp[i][j] += dp[i-1][k]\)
- 若 \(k \neq j/k\):\(dp[i][j] += dp[i-1][j/k]\)(避免重复计算平方根)
- 所有运算对 \(mod\) 取模
- 统计答案:\(ans = \sum_{j=1}^{n} dp[m][j] \mod mod\)
-
时间/空间复杂度:
- 时间复杂度:\(O(m \times n \times \sqrt{n})\),对于每个状态 \(dp[i][j]\) 需要枚举 \(j\) 的所有因数,约 \(O(\sqrt{j})\) 次操作
- 空间复杂度:\(O(m \times n)\),DP 数组存储
-
线性 DP 与因数枚举的核心思想:
- 状态设计:二维状态 \(dp[i][j]\) 同时记录序列长度和结尾数值,满足无后效性
- 倍数关系转化:后数是前数的倍数 \(\Leftrightarrow\) 前数是后数的因数,通过枚举因数实现状态转移
- 因数枚举优化:只枚举到 \(\sqrt{j}\),成对获取因数 \(k\) 和 \(j/k\),将复杂度从 \(O(j)\) 降为 \(O(\sqrt{j})\)
- 递推顺序:按长度从小到大递推,确保计算 \(dp[i][j]\) 时 \(dp[i-1][k]\) 已经计算完毕
- 适用于序列计数、约束传递、因数倍数关系类问题
【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// ================= 常量与全局变量 =================
const int N = 105; // 最大数值上限
const int mod = 1e9 + 7; // 模数
int n; // 数值上限(序列中每个数的取值范围为 1~n)
int m; // 序列长度
int ans; // 最终答案
int dp[N][N]; // 动态规划数组
// dp[i][j] 表示长度为 i、最后一个数为 j 的合法序列数量
// ================= 主函数 =================
int main()
{
// 读取数值上限和序列长度
cin >> n >> m;
// ========= 初始化:长度为 1 的序列 =========
// 长度为 1 时,最后一个数可以是 1~n 中的任意一个
for (int i = 1; i <= n; i++)
dp[1][i] = 1;
// ========= 动态规划递推 =========
// 从长度为 2 开始,逐步递推到长度为 m
for (int i = 2; i <= m; i++)
{
// 枚举当前序列最后一个数 j
for (int j = 1; j <= n; j++)
{
// 枚举 j 的所有因数 k
// 前一个数必须是 j 的因数,才能保证 j 能被前一个数整除
for (int k = 1; k * k <= j; k++)
{
if (j % k == 0) // k 是 j 的因数
{
// 将前一个数为 k 的长度为 i-1 的序列扩展为以 j 结尾
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;
// 如果 k 不等于 j/k,则 j/k 也是 j 的一个因数
if (k * k != j)
dp[i][j] = (dp[i][j] + dp[i - 1][j / k]) % mod;
}
}
}
}
// ========= 统计答案 =========
// 累加所有长度为 m 的合法序列数量
for (int j = 1; j <= n; j++)
ans = (ans + dp[m][j]) % mod;
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
4 3
13
浙公网安备 33010602011771号