题解:洛谷 P1679 神奇的四次方数
【题目来源】
【题目描述】
将一个整数 \(m\) 分解为 \(n\) 个四次方数的和的形式,要求 \(n\) 最小。例如,当 \(m=706\) 时,因为 \(706=5^4+3^4\),所以有 \(n=2\)。可以证明此时 \(n\) 最小。
【输入】
一行,一个整数 \(m\)。
【输出】
一行,一个整数 \(n\)。
【输入样例】
706
【输出样例】
2
【核心思想】
-
问题分析:给定整数 \(m\),需要将其分解为若干个四次方数的和,使得使用的四次方数个数最少。这是一个完全背包求最少个数问题,物品为四次方数 \(i^4\)(\(i = 1, 2, \dots\)),每个物品可以使用无限次,目标是用最少的物品凑出总和 \(m\)。
-
算法选择:
- 完全背包:每个四次方数可以使用无限次
- 求最小值版本:
dp[j]表示凑成数字 \(j\) 所需的最少四次方数个数 - 预处理四次方数:\(i^4\)(\(i = 1, 2, \dots, 18\)),因为 \(18^4 = 104976 > 10^5\)
-
关键步骤:
- 初始化:
dp[0] = 0(凑出0需要0个数),其余dp[j] = INF(无穷大) - 预处理物品:四次方数 \(x = i^4\),\(i\) 从 \(1\) 到 \(18\)(因为 \(18^4 > 10^5\))
- 完全背包转移:对于每个四次方数 \(x\),遍历 \(j\) 从 \(x\) 到 \(m\)
dp[j] = min(dp[j], dp[j - x] + 1)- 要么不使用当前的 \(x\),要么使用一个 \(x\)(个数+1)
- 答案获取:
dp[m]即凑出 \(m\) 所需的最少四次方数个数
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(k \times m)\),其中 \(k = 18\) 为四次方数个数,\(m\) 为目标数
- 空间复杂度:\(O(m)\),一维DP数组
-
完全背包求最少个数的核心思想:
- 物品无限使用:完全背包允许每个物品使用任意次数
- 正向遍历:与01背包不同,完全背包使用正向遍历
j从 \(x\) 到 \(m\) - 最小值转移:
dp[j] = min(dp[j], dp[j - x] + 1),表示使用一个 \(x\) 后的最小个数 - 预处理优化:先计算出所有可能的四次方数,避免重复计算
- 适用于"用最少物品凑出目标和"的完全背包问题
【算法标签】
普及- #完全背包
【代码详解】
#include<bits/stdc++.h>
using namespace std;
int n, dp[100005]; // n: 目标数,dp: 动态规划数组
int main()
{
cin >> n; // 读入目标数
memset(dp, 0x3f, sizeof(dp)); // 初始化dp数组为无穷大
dp[0] = 0; // 凑出0需要的数字个数为0
// 完全背包问题
for (int i = 1; i <= 18; i++) // 遍历所有可能的四次方数
{
int x = i * i * i * i; // 计算i的四次方
for (int j = x; j <= n; j++) // 从x开始遍历到n
{
// 状态转移方程:dp[j] = min(dp[j], dp[j-x] + 1)
// 要么不使用当前的x,要么使用当前的x
dp[j] = min(dp[j], dp[j - x] + 1);
}
}
cout << dp[n]; // 输出凑出n所需的最小数字个数
return 0;
}
【运行结果】
706
2
浙公网安备 33010602011771号