题解:P3861 拆分
JunjieCharles · · 题解
题目大意
给定一个整数
核心思路:01 背包方案计数
这道题的本质是一个变形的 01 背包方案计数问题。普通的 01 背包是“体积相加等于总容量”,而这里是“因数相乘等于总乘积”。物品是
状态转移
如果不考虑状态值域的大小,我们可以自然地定义二维状态
在普通的 01 背包中,因为第
离散化状态值域
目标乘积
注意到一个重要性质:我们只关心那些能整除
重新定义一维状态
其中
由于
常数优化:利用因子对称性进行数组映射
在实际实现状态转移时,我们面临一个问题:如何快速知道 std::unordered_map)来记录每个因数对应的下标。然而,在
利用因数成对出现的对称性,
因此我们只需要开一个大小为
- 当
x \le \sqrt{n} 时,直接查表返回id_x 。 - 当
x > \sqrt{n} 时,通过查表求出\frac{n}{x} 的下标,返回m - 1 - id_{\frac{n}{x}} 。
这样,我们将哈希查找转化为了数组寻址,虽然单次操作理论复杂度都是
代码实现
#include <bits/stdc++.h>
using namespace std;
using T = long long;
constexpr int M = 998244353;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
T n;
cin >> n;
// 预处理因数
vector<T> fac;
for (T i = 1; i * i <= n; i++) {
if (n % i == 0) {
fac.push_back(i);
if (i * i != n) fac.push_back(n / i);
}
}
sort(fac.begin(), fac.end());
int m = (int)fac.size();
T sq = sqrt(n);
// 获取任意因数在 fac 数组中的下标
vector<int> id(sq + 2);
for (int i = 0; fac[i] <= sq; i++) {
id[fac[i]] = i;
}
auto get_id = [&](T x) {
return x <= sq ? id[x] : m - 1 - id[n / x];
};
// 01 背包 DP
vector<int> dp(m, 0);
dp[0] = 1;
// 遍历所选因子,不能选 1 和 n 本身,所以下标从 1 到 m - 2
for (int i = 1; i < m - 1; i++) {
T rem = n / fac[i];
int r = get_id(rem);
// 倒序遍历容量,保证每个物品只选一次
for (int j = r; j >= 0; j--) {
if (rem % fac[j] == 0) {
int k = get_id(fac[i] * fac[j]);
dp[k] += dp[j];
if (dp[k] >= M) dp[k] -= M;
}
}
}
cout << dp[m - 1] << '\n';
}
return 0;
}
撰写本题解时,该代码为最优解第二名。
AI 使用说明 本题解在写作完成后使用 Gemini 进行了润色。