题解:学而思编程 倍数序列2

【题目来源】

学而思编程:倍数序列2

【题目描述】

要求找到一个正整数序列,满足序列中所有的数不超过 \(n\)序列长度为 \(m\),且除了第一个数外,所有的数都能被前一个数整除(即是前一个数的倍数)。

我们想要知道这样的序列有多少个,输出所有满足要求的序列的个数,取除以 \(10^9+7\) 的余数。

【输入】

第一行,两个正整数 \(n\)\(m\)

【输出】

输出一个满足要求的序列的个数,取除以 \(10^9+7\) 的余数。

【输入样例】

4 3

【输出样例】

13

【核心思想】

  1. 问题分析:给定数值上限 \(n\) 和序列长度 \(m\),求满足以下条件的正整数序列数量:

    • 序列中所有数不超过 \(n\)
    • 序列长度为 \(m\)
    • 除第一个数外,每个数都能被前一个数整除(即后数是前数的倍数)
      这是一个线性动态规划问题,状态需要记录序列长度和最后一个数的值。
  2. 算法选择

    • 线性 DP(二维)\(dp[i][j]\) 表示长度为 \(i\)、最后一个数为 \(j\) 的合法序列数量
    • 因数枚举:通过枚举 \(j\) 的所有因数来转移状态
    • 取模运算:答案对 \(10^9+7\) 取模
  3. 关键步骤

    • 初始化\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \times n \times \sqrt{n})\),对于每个状态 \(dp[i][j]\) 需要枚举 \(j\) 的所有因数,约 \(O(\sqrt{j})\) 次操作
    • 空间复杂度:\(O(m \times n)\),DP 数组存储
  5. 线性 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
posted @ 2026-06-17 17:24  团爸讲算法  阅读(7)  评论(0)    收藏  举报