题解:学而思编程 购买模型

【题目来源】

学而思编程:购买模型

【题目描述】

商店里有 \(n\) 个模型,编号为 \(1∼n\),第 \(i\) 个模型的尺寸为 \(s_i\)​。

小猴想要购买若干个模型,他对购买的模型有以下要求:

(1)编号从小到大排序后,对所有相邻的模型,后一个的编号是前一个的倍数。

(2)编号从小到大排序后,对所有相邻的模型,后一个的尺寸严格大于前一个的尺寸。

我们认为只购买 \(1\) 个模型也满足要求。

例如:有 \(6\) 个模型,大小是 \([3,6,7,3,7,7]\),那么小猴可以买第 \(1,2,6\) 个模型,它们的尺寸是 $[3,6,7];但是小猴不可以购买第 \(1,2,3\) 个,因为编号 \(3\) 不是它前一个编号 \(2\) 的倍数;小猴也不可以购买第 \(1,2,4\) 个,因为这三个的尺寸 \([3,6,3]\) 不是严格升序。

小猴想知道他最多可以购买多少个模型。输入包含多组数据。

【输入】

\(1\) 行,\(1\) 个正整数 \(T\),表示有 \(T\) 组数据。

接下来 \(2T\) 行,每 \(2\) 行表示 \(1\) 组数据:

每组数据第 \(1\) 行,\(1\) 个正整数 \(n\),表示模型个数。

每组数据第 \(2\) 行,\(n\) 个正整数 \(s_1,s_2,⋯,s_n\)​,表示模型尺寸。

【输出】

输出 \(T\) 行,每行输出一个整数表示每组数据的答案。

【输入样例】

4
4
5 3 4 6
7
1 4 2 3 6 4 9
5
5 4 3 2 1
1
9

【输出样例】

2
3
1
1

【核心思想】

  1. 问题分析:给定 \(n\) 个模型,每个模型有尺寸 \(s_i\),要求选出最长的子序列满足两个条件:编号上后一个是前一个的倍数,尺寸上严格递增。这是一个线性动态规划 + 因子枚举优化问题,关键在于将"倍数编号"约束转化为因子关系,并通过枚举优化降低转移复杂度。

  2. 算法选择

    • 线性 DPdp[i] 表示以第 \(i\) 个模型结尾的满足条件的最长序列长度
    • 因子枚举优化:利用 \(j \mid i\)\(i\)\(j\) 的倍数这一性质,将 \(O(n^2)\) 的转移优化为 \(O(\sqrt{n})\) 每点
  3. 关键步骤

    • 初始化:读取 \(T\)(测试组数),对每组数据:
      • 读取 \(n\) 和尺寸数组 \(s[1..n]\)
      • dp[i] = 1\(1 \leq i \leq n\)):每个模型单独构成长度为 \(1\) 的序列
    • DP 转移(枚举 \(i\)\(1\)\(n\) 作为序列结尾):
      • 枚举 \(j\)\(1\)\(\sqrt{i}\)
        • \(i \bmod j = 0\)\(j\)\(i\) 的因子):
          • 因子 \(j\):若 \(j < i\)\(s_j < s_i\)
            • dp[i] = \max(dp[i], dp[j] + 1):从以 \(j\) 结尾的序列扩展
          • 配对因子 \(i/j\):若 \(i/j \neq j\)\(i/j < i\)\(s_{i/j} < s_i\)
            • dp[i] = \max(dp[i], dp[i/j] + 1):同时处理配对因子
      • ans = \max(ans, dp[i]):更新全局最长序列长度
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \sqrt{n})\),每个位置 \(i\) 枚举到 \(\sqrt{i}\),同时处理两个因子
    • 空间复杂度:\(O(n)\),DP 数组 dp[1..n] 和尺寸数组 s[1..n]
  5. 因子枚举优化的核心思想

    • 倍数约束转化:"后一个编号是前一个的倍数"等价于"前一个编号是后一个的因子",即若当前结尾为 \(i\),则前驱 \(j\) 必须满足 \(j \mid i\)
    • 枚举优化:不遍历所有 \(j < i\),而是枚举 \(i\) 的因子,因子个数为 \(O(\sqrt{i})\),远小于 \(O(i)\)
    • 配对因子处理:枚举 \(j\)\(\sqrt{i}\) 时,若 \(j \mid i\)\(i/j\) 也是因子,一次判断处理两个转移,避免遗漏
    • 去重处理:当 \(j \times j = i\)\(j = i/j\),通过 other != j 避免重复计算同一因子
    • DP 最优子结构dp[i] 只依赖于编号为 \(i\) 的因子的 dp 值,满足无后效性,按编号从小到大枚举即可保证正确性
    • 适用于"编号具有整除约束 + 值域具有单调约束"的最长子序列问题,因子枚举可将平方级转移优化至根号级

【算法标签】

线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;           // 最大模型数量
int T, n;                       // T: 测试数据组数, n: 模型个数
int s[N];                       // s[i]: 第i个模型的尺寸
int dp[N];                      // dp[i]: 以第i个模型结尾的满足条件的最长序列长度

int main()
{
    cin >> T;                   // 读入测试数据组数
    while (T--)                 // 依次处理每组数据
    {
        cin >> n;               // 读入模型个数

        // 读入每个模型的尺寸
        for (int i = 1; i <= n; i++)
            cin >> s[i];

        // 初始化:每个模型单独构成长度为1的序列
        for (int i = 1; i <= n; i++)
            dp[i] = 1;

        int ans = 1;            // 答案:至少可以购买1个模型

        // 动态规划:枚举每个模型作为序列结尾
        for (int i = 1; i <= n; i++)
        {
            // 枚举i的所有因子j(j < i),检查是否能从j转移过来
            // 优化:只枚举到sqrt(i),同时处理因子j和配对因子i/j
            for (int j = 1; j * j <= i; j++)
            {
                if (i % j == 0)             // j是i的因子
                {
                    // 情况1:j是i的因子,且j < i
                    // 条件:编号j是i的因子(即i是j的倍数),且尺寸严格递增
                    if (s[j] < s[i])
                        dp[i] = max(dp[i], dp[j] + 1);

                    // 情况2:i/j也是i的因子(j的配对因子)
                    int other = i / j;
                    // 避免重复计算(当j*j=i时,other==j,跳过)
                    // 条件:other < i,且尺寸严格递增
                    if (other != j && s[other] < s[i])
                        dp[i] = max(dp[i], dp[other] + 1);
                }
            }
            ans = max(ans, dp[i]);          // 更新全局最大值
        }

        cout << ans << endl;                // 输出当前测试数据的答案
    }

    return 0;
}

【运行结果】

4
4
5 3 4 6
2
7
1 4 2 3 6 4 9
3
5
5 4 3 2 1
1
1
9
1
posted @ 2026-07-04 12:19  团爸讲算法  阅读(6)  评论(0)    收藏  举报