题解:学而思编程 真因数之和
【题目来源】
学而思编程:真因数之和
【题目描述】
完全数指的是恰好等于自己全部真因数之和的数,这样的数十分稀少。
我们现在把条件放宽一点,想要从 \(n\) 的真因数中可以选出若干个不同数,使得它们的和等于 \(n\)。我们想知道有多少种取数的方式,输入包含多组数据。
例如 \(n=24\) 的真因数有 \(1,2,3,4,6,8,12\) 共 \(7\) 个,取出若干不同数使得和等于 \(24\) 的方法有 \(5\) 种:
\(1+2+3+6+12\),\(1+3+8+12\),\(2+4+6+12\),\(4+8+12\),\(1+2+3+4+6+8\)。
【输入】
第 \(1\) 行,\(1\) 个正整数 \(T\)
接下来 \(T\) 行,每行 \(1\) 个正整数 \(n\)
【输出】
输出 \(T\) 行,对输入中每个 \(n\),输出取数方法数。
【输入样例】
3
6
24
15
【输出样例】
1
5
0
【核心思想】
-
问题分析:给定 \(T\) 个查询,每个查询给出一个正整数 \(n\),求从 \(n\) 的真因数中选出若干个不同的数,使得它们的和等于 \(n\) 的方案数。真因数是指小于 \(n\) 且能整除 \(n\) 的正整数。这是一个预处理 + 计数型01背包问题,关键在于预处理所有数的真因数,然后对每个查询使用01背包计算方案数。
-
算法选择:
- 预处理真因数:使用筛法思想,对于每个因数 \(i\),将其加入所有倍数 \(j\)(\(j = 2i, 3i, ...\))的真因数列表中
- 计数型01背包:
dp[j]表示和为 \(j\) 的方案数,状态转移dp[j] += dp[j - num],逆序枚举保证每个数只用一次 - 多组查询处理:预处理所有数的真因数,每个查询独立做01背包
-
关键步骤:
- 预处理真因数(筛法):
- 枚举 \(i\) 从 \(1\) 到 \(MAXN\)
- 对于每个 \(i\),枚举其倍数 \(j = 2i, 3i, ... \leq MAXN\)
- 将 \(i\) 加入
a[j](\(j\) 的真因数列表)
- 处理每个查询 \(x\):
- 初始化
dp[0] = 1(和为0的方案数为1,空集),其余dp[i] = 0 - 01背包计数:遍历 \(x\) 的每个真因数 \(num\)
- 逆序枚举 \(j\) 从 \(x\) 到 \(num\):
dp[j] += dp[j - num]
- 逆序枚举 \(j\) 从 \(x\) 到 \(num\):
- 返回
dp[x](和为 \(x\) 的方案数)
- 初始化
- 预处理真因数(筛法):
-
时间/空间复杂度:
- 预处理时间复杂度:\(O(MAXN \log MAXN)\),每个数 \(i\) 贡献给 \(MAXN/i\) 个倍数
- 单次查询时间复杂度:\(O(d(n) \times n)\),\(d(n)\) 为 \(n\) 的真因数个数,最坏情况约为 \(O(n \sqrt{n})\)
- 总时间复杂度:\(O(MAXN \log MAXN + T \times d(n) \times n)\)
- 空间复杂度:\(O(MAXN \times \text{平均真因数个数})\),存储所有数的真因数列表
-
计数型01背包与预处理的核心思想:
- 计数型01背包:与最值型不同,
dp[j]表示方案数而非最大价值,状态转移为累加而非取最值 - 逆序枚举:保证每个物品(真因数)只被使用一次,避免重复计算
- 筛法预处理:利用倍数关系一次性预处理所有数的真因数,避免对每个查询单独分解质因数
- 空集初始化:
dp[0] = 1表示和为0有一种方案(不选任何数),是计数型背包的基础 - 适用于子集和方案数统计、真因数分解、多组查询类问题
- 计数型01背包:与最值型不同,
【算法标签】
01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义长整型别名,便于处理大数据
#define int long long
// ================= 常量与全局变量 =================
const int N = 10005; // 数值上限
int n; // 查询次数
vector<int> a[N]; // a[x] 存储 x 的所有真因数(不包括 x 本身)
int dp[N]; // 动态规划数组,用于计算分解方案数
// ================= 预处理:求每个数的所有真因数 =================
void init()
{
// 枚举所有可能的因数 i
for (int i = 1; i <= 10000; i++)
{
// j 是 i 的倍数,即 i 是 j 的真因数
for (int j = i + i; j <= 10000; j += i)
{
a[j].push_back(i); // 将 i 加入 j 的真因数列表
}
}
}
// ================= 计算将 x 分解为其真因数之和的方案数 =================
int solve(int x)
{
// 初始化 dp 数组
memset(dp, 0, sizeof(dp));
dp[0] = 1; // 和为 0 的方案数为 1(空集)
// 01 背包:每个真因数只能使用一次
for (int num : a[x]) // 遍历 x 的所有真因数
{
for (int j = x; j >= num; j--) // 逆序枚举,保证每个数只用一次
{
dp[j] += dp[j - num]; // 累加方案数
}
}
return dp[x]; // 返回和为 x 的方案数
}
// ================= 主函数 =================
signed main()
{
// 预处理所有数的真因数
init();
// 读取查询次数
cin >> n;
// 处理每个查询
while (n--)
{
int x;
cin >> x; // 读取目标数
cout << solve(x) << endl; // 输出分解方案数
}
return 0;
}
【运行结果】
3
6
1
24
5
15
0
浙公网安备 33010602011771号