1 条题解

  • 0
    @ 2026-10-5 9:26:25

    我们可以这样思考正向的“求子集和”过程:

    假设我们已经知道了一部分数,它们构成的子集和频率数组为 boldb_{old}。现在我们要向其中加入一个新的正整数 kk。加入 kk 后,新的子集和不仅包含了原来的所有和,还包含了“原来的和加上 kk”的新和。

    因此,加入 kk 后的新频率数组 bnewb_{new} 满足:

    bnew[i]=bold[i]+bold[i−k]b_{new}[i] = b_{old}[i] + b_{old}[i - k]

    即 01 背包方案数问题。

    本题要求我们从最终的 bnewb_{new}(也就是题目给的 bb 数组)反推原来的元素。

    因为所有的 aia_i 都是正整数,所以在排除了 0 之后,数组 bb 中第一个频率大于 0 的下标 kk(即 bk>0b_k > 0 且 k>0k > 0)必定是数组 aa 中的最小元素。为什么?因为没有任何更小的正整数可以组合出 kk 了。

    既然我们找到了一个元素 kk,我们就需要把它的影响从 bb 数组中“剥离”出去。根据上面的公式,我们可以逆向还原出没有加入 kk 之前的频率数组:

    bold[i]=bnew[i]−bold[i−k]b_{old}[i] = b_{new}[i] - b_{old}[i - k]

    由于计算 bold[i]b_{old}[i] 时需要用到 bold[i−k]b_{old}[i-k],所以我们只需要从左到右遍历 ii,就可以在原数组上原地完成剥离操作。

    剥离一次后,我们就找到了一个元素。只要我们重复这个“寻找最小非零项并剥离”的过程 nn 次,就能找出所有的 a1,a2,…,ana_1, a_2, \dots, a_n。

    以题目中的 样例 1 为例:n=2,m=3n=2, m=3,初始数组 b=[1,1,1,1]b = [1, 1, 1, 1]。

    • 第一步:忽略 b0b_0,寻找第一个大于 0 的项。发现 b1=1>0b_1 = 1 > 0,因此我们的第一个元素是 a1=1a_1 = 1。

    • 第二步:从 bb 中剥离元素 1 的影响。

      • i=1:b1=b1−b0=1−1=0i = 1: b_1 = b_1 - b_0 = 1 - 1 = 0
      • i=2:b2=b2−b1=1−0=1i = 2: b_2 = b_2 - b_1 = 1 - 0 = 1 (注意这里减去的是更新后的 b1b_1)
      • i=3:b3=b3−b2=1−1=0i = 3: b_3 = b_3 - b_2 = 1 - 1 = 0

      剥离后,数组变为 b=[1,0,1,0]b = [1, 0, 1, 0]。

    • 第三步:在新的 bb 数组中,继续寻找第一个大于 0 的项。发现 b2=1>0b_2 = 1 > 0,因此第二个元素是 a2=2a_2 = 2。

    • 第四步:从 bb 中剥离元素 2 的影响。

      • i=2:b2=b2−b0=1−1=0i = 2: b_2 = b_2 - b_0 = 1 - 1 = 0
      • i=3:b3=b3−b1=0−0=0i = 3: b_3 = b_3 - b_1 = 0 - 0 = 0

      剥离后,数组变为 b=[1,0,0,0]b = [1, 0, 0, 0]。

    此时我们已经找到了 n=2n=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;
    }
    
    

    信息

    ID
    2774
    时间
    1000ms
    内存
    128MiB
    难度
    5
    标签
    (无)
    递交数
    19
    已通过
    10
    上传者