题目要求将一个正整数 ( n ) 拆分成若干个完全平方数(如 ( 1,4,9,16,\dots ))的和,且希望使用的完全平方数的个数最少。例如 ( n = 12 ) 时,可以拆成 ( 4 + 4 + 4 )(3个数),也可以拆成 ( 9 + 1 + 1 + 1 )(4个数),但不难找到更优的方案 ( 4 + 4 + 4 ) 或 ( 9 + 1 + 1 + 1 ) — 实际上 12 最少需要 3 个平方数。本题就是求对于给定的 ( n ),最少需要多少个完全平方数。
这是一个典型的动态规划问题。
定义:
边界条件:
状态转移:
最终答案即为 ( dp[n] )。
由于计算 ( dp[i] ) 时,所依赖的状态 ( dp[i - j^2] ) 一定比 ( i ) 小,因此按 ( i ) 从小到大的顺序递推是正确的。
空间复杂度为 ( O(n) ),存储 ( dp ) 数组。
参考代码与上述思路完全一致,下面给出带有详细注释的版本:
cpp1#include <iostream> 2#include <cmath> 3using namespace std; 4 5int main() { 6 int n; 7 cin >> n; 8 9 // dp[i] 表示 i 最少由几个完全平方数组成 10 int dp[n + 1]; 11 12 // 初始化 13 dp[0] = 0; // 和为0需要0个数 14 for (int i = 1; i <= n; i++) { 15 dp[i] = i; // 最坏情况:全用 1 相加 16 } 17 18 // 动态规划 19 for (int i = 1; i <= n; i++) { 20 // 枚举最后一个完全平方数 j*j 21 for (int j = 1; j * j <= i; j++) { 22 // 状态转移:从 i - j*j 的方案再加上一个 j*j 23 dp[i] = min(dp[i], dp[i - j * j] + 1); 24 } 25 } 26 27 // 输出结果 28 cout << dp[n] << endl; 29 return 0; 30}
代码解释:
dp[i] = i 是一种简单的上界初始值,因为我们总可以用 ( i ) 个 ( 1^2 ) 相加得到 ( i )。j * j <= i 保证了枚举的平方数不超过当前数。min(dp[i], dp[i - j * j] + 1) 表示若最后一个平方数是 ( j^2 ),那么剩下的 ( i - j^2 ) 需要的最小个数加上 1,即为新的可能答案,我们取最小值。dp[n] 就是我们要求的最小数量。本题是完全背包问题的一个变形,使用动态规划可以高效求解。关键点在于正确设计状态和转移方程,并注意初始化和枚举顺序。掌握这种方法后,类似“将一个数拆分成某种特定数字的和,求最少个数”的题目都可以用同一种思路解决。