题解:洛谷 P6065 Sumsets
【题目来源】
洛谷:P6065 [USACO05JAN] Sumsets S - 洛谷
【题目描述】
给出一个整数 \(N\),将 \(N\) 分解为若干个 \(2\) 的次幂的和,共有多少种方法?
【输入】
输入一个整数 \(N\)(\(1 \leq N \leq 10^6\))。
【输出】=-
输出方案数对 \(10^9\) 取模的结果。
【输入样例】
7
【输出样例】
6
【核心思想】
-
问题分析:给定整数 \(N\),求将 \(N\) 分解为若干个 \(2\) 的次幂之和的方案数(即 \(N = 2^{k_1} + 2^{k_2} + \dots + 2^{k_m}\),允许重复使用相同的 \(2\) 的次幂)。本质是一个受限整数拆分问题,等价于用 \(\{1, 2, 4, 8, \dots\}\) 这些面额的"硬币"凑成金额 \(N\) 的方案数。
-
算法选择:
- 递推/动态规划:利用奇偶性分析,发现 \(N\) 的拆分方案数与 \(N-1\) 和 \(N/2\) 存在递推关系,\(O(N)\) 递推求解
-
关键步骤:
- 初始化:\(\text{dp}[1] = 1\)(\(1 = 2^0\),唯一方案),\(\text{dp}[2] = 2\)(\(2 = 2^1\) 或 \(2 = 1 + 1\)),\(\text{dp}[3] = 2\),\(\text{dp}[4] = 4\)
- 递推关系(\(i\) 从 \(5\) 到 \(N\)):
- \(i\) 为奇数:\(\text{dp}[i] = \text{dp}[i-1]\)。因为奇数拆分中必然包含至少一个 \(1\)(\(2^0\)),去掉这个 \(1\) 后恰好对应 \(i-1\) 的拆分方案,一一对应
- \(i\) 为偶数:\(\text{dp}[i] = \text{dp}[i-1] + \text{dp}[i/2]\)。
- \(\text{dp}[i-1]\):包含至少一个 \(1\) 的拆分方案(去掉一个 \(1\) 对应 \(i-1\) 的拆分)
- \(\text{dp}[i/2]\):不包含 \(1\) 的拆分方案(所有项都是偶数,除以 \(2\) 后对应 \(i/2\) 的拆分)
- 取模处理:每次递推后对 \(10^9\) 取模
- 输出答案:\(\text{dp}[N]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),线性递推
- 空间复杂度:\(O(N)\),一维 \(\text{dp}\) 数组
-
奇偶分类递推的核心思想:
- 奇数的强制含1性:任何奇数的 \(2\) 的次幂拆分必然包含 \(2^0 = 1\)(因为其他项都是偶数,偶数和为偶数,奇数必须加 \(1\)),因此去掉这个 \(1\) 后与 \(i-1\) 的方案一一对应
- 偶数的二分拆解:偶数 \(i\) 的拆分分为两类——包含 \(1\) 的(对应 \(\text{dp}[i-1]\))和不包含 \(1\) 的(所有项 \(\geq 2\),整体除以 \(2\) 后对应 \(i/2\) 的拆分)
- 递推而非完全背包:利用奇偶性质将完全背包的 \(O(N \log N)\) 优化到 \(O(N)\),关键在于发现"不含 \(1\) 的偶数拆分"与" \(i/2\) 的任意拆分"之间的缩放对应关系
- 模数处理:由于 \(N \leq 10^6\),方案数极大,每步取模防止溢出
- 适用于受限整数拆分、硬币组合、递推关系发现类问题
【解题思路】

【算法标签】
普及 #递推
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, dp[1000005]={0, 1, 2, 2, 4};
int main()
{
cin >> n; // 输入n
for (int i=5; i<=n; i++) { // 遍历5到n,进行递推
if (i%2==1) { // 当i为奇数时
dp[i] = dp[i-1]; // i的方案数与i-1相同
} else { // 当i为偶数时
dp[i] = dp[i/2] + dp[i-1]; // i的方案数为i/2的方案数加上i-1的方案数
}
dp[i] %= 1000000000; // 每计算一次进行一次取模运算
}
cout << dp[n] << endl; // 输出n的方案数
return 0;
}
【运行结果】
7
6
浙公网安备 33010602011771号