题意:
原有集合 A,基数为 n,其有 2 ^ n 个子集,对每个子集分别求和,对每个和 S,B[S]++.
现给出 B,要求集合 A(按字典序排列)
思路:
也可以考虑当前已经找到的数字,按照生成 B 数组的方式,再开一个 f,把已经找到的数字产生的影响往 f 里面扔。
从小到大去找,比较 b 与 f 的差别,如果 f[p] < b[p],说明当前的 p 是一个新的元素。
p 不可能是 已有的一些元素 与 某个新的元素 的和,如果是这样,那么新的元素必然已经在前面出现并且找到过。
官方题解:
签到题,大致的思想就是反过来的背包。
如果 Bi 是 B 数组中除了 B0 以外第一个值不为 0 的位置,那么显然 i 就是 A 中的最小数。
现在需要求出删掉 i 后的 B 数组,过程大概是反向的背包,即从小到大让 Bj−=Bj−i。
时间复杂度 O(nm)。
#include <bits/stdc++.h>
#define maxn 10010
typedef long long LL;
LL b[maxn], ans[55], f[maxn];
void work() {
int n, m;
scanf("%d%d", &n, &m);
memset(f, 0, sizeof(f));
for (int i = 0; i <= m; ++i) scanf("%lld", &b[i]);
f[0] = 1;
int p = 0, tot = 0;
while (true) {
if (b[p] == f[p]) ++p;
else {
ans[tot++] = p;
if (tot == n) break;
for (int i = m; i >= p; --i) f[i] += f[i - p];
}
}
printf("%lld", ans[0]);
for (int i = 1; i < tot; ++i) printf(" %lld", ans[i]);
printf("\n");
}
int main() {
int T;
scanf("%d", &T);
while (T--) work();
return 0;
}
心塞...出题人说这是签到题Orz
我一开始还十分天真的认为最大的那个肯定是基数为 n 的集合,往下依次找到的 n 个就是基数为 n - 1 的集合,然后还光荣得WA了一发;这种想法就跟认为最小的 n 个数就是 A 集合中的 n 个数一样愚蠢...
最后还是学姐解决了的(躺倒