题解:学而思编程 购买模型
【题目来源】
学而思编程:购买模型
【题目描述】
商店里有 \(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
【核心思想】
-
问题分析:给定 \(n\) 个模型,每个模型有尺寸 \(s_i\),要求选出最长的子序列满足两个条件:编号上后一个是前一个的倍数,尺寸上严格递增。这是一个线性动态规划 + 因子枚举优化问题,关键在于将"倍数编号"约束转化为因子关系,并通过枚举优化降低转移复杂度。
-
算法选择:
- 线性 DP:
dp[i]表示以第 \(i\) 个模型结尾的满足条件的最长序列长度 - 因子枚举优化:利用 \(j \mid i\) 时 \(i\) 是 \(j\) 的倍数这一性质,将 \(O(n^2)\) 的转移优化为 \(O(\sqrt{n})\) 每点
- 线性 DP:
-
关键步骤:
- 初始化:读取 \(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):同时处理配对因子
- 因子 \(j\):若 \(j < i\) 且 \(s_j < s_i\):
- 若 \(i \bmod j = 0\)(\(j\) 是 \(i\) 的因子):
ans = \max(ans, dp[i]):更新全局最长序列长度
- 枚举 \(j\) 从 \(1\) 到 \(\sqrt{i}\):
- 输出
ans
- 初始化:读取 \(T\)(测试组数),对每组数据:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \sqrt{n})\),每个位置 \(i\) 枚举到 \(\sqrt{i}\),同时处理两个因子
- 空间复杂度:\(O(n)\),DP 数组
dp[1..n]和尺寸数组s[1..n]
-
因子枚举优化的核心思想:
- 倍数约束转化:"后一个编号是前一个的倍数"等价于"前一个编号是后一个的因子",即若当前结尾为 \(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
浙公网安备 33010602011771号