题解:洛谷 P11246 小杨和整数拆分
【题目来源】
洛谷:P11246 [GESP202409 六级] 小杨和整数拆分 - 洛谷
【题目描述】
小杨有一个正整数\(n\),小杨想将它拆分成若干完全平方数的和,同时小杨希望拆分的数量越少越好。
小杨请你编写程序计算出总和为\(n\) 的完全平方数的最少数量。
【输入】
第一行包含一个正整数\(n\),含义如题面所示。
【输出】
输出一个整数,代表总和为\(n\) 的完全平方数的最少数量。
【输入样例】
18
【输出样例】
2
【核心思想】
-
问题分析:给定正整数 \(n\),将其拆分为若干完全平方数之和,求最少拆分数量。这是一个完全背包动态规划问题,核心在于将问题转化为:用面值为 \(1^2, 2^2, 3^2, \ldots\) 的"硬币"凑成金额 \(n\),求最少硬币数。
-
算法选择:
- 一维线性 DP:设 \(dp[i]\) 表示总和为 \(i\) 的完全平方数的最少数量
- 完全背包转移:\(dp[i] = \min_{j^2 \leq i}(dp[i - j^2] + 1)\)
-
关键步骤:
- 初始化:\(dp[0] = 0\),\(dp[i] = i\)(最坏情况用 \(i\) 个 \(1^2\))
- DP 计算(\(i\) 从 \(1\) 到 \(n\)):
- \(dp[i] \leftarrow i\)(初始化为最坏情况)
- 枚举 \(j\) 从 \(1\) 到 \(\lfloor \sqrt{i} \rfloor\):
- \(dp[i] \leftarrow \min(dp[i], dp[i - j^2] + 1)\)
- 输出结果:\(dp[n]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot \sqrt{n})\),外层 \(n\),内层 \(\sqrt{n}\)
- 空间复杂度:\(O(n)\),一维 \(dp\) 数组
-
完全背包 DP 的核心思想:
- 状态定义:\(dp[i]\) 表示组成和为 \(i\) 所需的最少完全平方数个数,具有最优子结构——\(dp[i]\) 可由更小的 \(dp[i - j^2]\) 推导而来
- 完全物品特性:每个完全平方数 \(j^2\) 可以使用无限次,因此只需从小到大枚举 \(i\),每次考虑所有可能的 \(j^2 \leq i\)
- 初始化的边界意义:\(dp[i] = i\) 对应全部用 \(1^2\) 组成,这是上界保证,确保 \(\min\) 操作有合法初值
- 数学背景:该问题与拉格朗日四平方和定理相关——任何正整数可表示为不超过 \(4\) 个完全平方数之和,因此答案范围很小(\(1\)-\(4\)),但 DP 方法通用且易于理解
- 适用于无限选取、求最少数量的组合优化类 DP 问题
【算法标签】
普及- #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5; // 定义最大范围
int n; // 输入的数字
int dp[N]; // dp数组
int main()
{
cin >> n; // 输入n
// 动态规划计算
for (int i = 1; i <= n; i++) {
dp[i] = i; // 初始值:最坏情况,全部用1^2表示
for (int j = 1; j <= sqrt(i); j++) { // j从1到√i
dp[i] = min(dp[i - j*j] + 1, dp[i]);
}
}
cout << dp[n]; // 输出结果
return 0;
}
【运行结果】
18
2
浙公网安备 33010602011771号