1 条题解
-
0
我们可以这样思考正向的“求子集和”过程:
假设我们已经知道了一部分数,它们构成的子集和频率数组为 。现在我们要向其中加入一个新的正整数 。加入 后,新的子集和不仅包含了原来的所有和,还包含了“原来的和加上 ”的新和。
因此,加入 后的新频率数组 满足:
即 01 背包方案数问题。
本题要求我们从最终的 (也就是题目给的 数组)反推原来的元素。
因为所有的 都是正整数,所以在排除了 0 之后,数组 中第一个频率大于 0 的下标 (即 且 )必定是数组 中的最小元素。为什么?因为没有任何更小的正整数可以组合出 了。
既然我们找到了一个元素 ,我们就需要把它的影响从 数组中“剥离”出去。根据上面的公式,我们可以逆向还原出没有加入 之前的频率数组:
由于计算 时需要用到 ,所以我们只需要从左到右遍历 ,就可以在原数组上原地完成剥离操作。
剥离一次后,我们就找到了一个元素。只要我们重复这个“寻找最小非零项并剥离”的过程 次,就能找出所有的 。
以题目中的 样例 1 为例:,初始数组 。
-
第一步:忽略 ,寻找第一个大于 0 的项。发现 ,因此我们的第一个元素是 。
-
第二步:从 中剥离元素 1 的影响。
- (注意这里减去的是更新后的 )
剥离后,数组变为 。
-
第三步:在新的 数组中,继续寻找第一个大于 0 的项。发现 ,因此第二个元素是 。
-
第四步:从 中剥离元素 2 的影响。
剥离后,数组变为 。
此时我们已经找到了 个元素,分别是 1 和 2。
#include <iostream> #include <vector> using namespace std; #define M 10005 #define LL long long int n, m; LL b[M]; vector<int> a; int main() { cin >> n >> m; for (int i = 0; i <= m; i++) cin >> b[i]; // 从 1 到 m 寻找最小的可用元素 for (int i = 1; i <= m; i++) { // while 循环处理如果有多个相同的元素 (比如有两个 2 的情况) while (b[i] > 0) { a.push_back(i); // 记录找到的元素 // 剥离元素 i 的影响 (相当于逆向的完全背包/01背包操作) for (int j = i; j <= m; j++) { b[j] -= b[j - i]; } // 如果已经找齐了 n 个数,直接退出 if (a.size() == n) break; } if (a.size() == n) break; } for (int v : a) cout << v << ' '; return 0; } -
- 1
信息
- ID
- 2774
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- (无)
- 递交数
- 19
- 已通过
- 10
- 上传者