此题已自我实现,但仍归于无码专区
本题在考场上就过了,所以难度并不高,发现性质即可。
problem
有 nnn 个正整数 a1,a2,...,ana_1,a_2,...,a_na1,a2,...,an,他们的和为 mmm。你想对于其每一个子集 SSS,求出他们的和。
给定 2n2^n2n 个 [0,m][0,m][0,m] 之间的和,其中数字 iii 出现了 bib_ibi 次。
求还原 aaa,数据保证有唯一解。
n≤50,m≤10000,1s,128MBn\le 50,m\le 10000,1s,128MBn≤50,m≤10000,1s,128MB
my idea
首先就能知道 b0,bmb_0,b_mb0,bm 一定是 111。
马上就发现最小的 aia_iai 是没有能被其他数组合出来的情况的,因为他们全是正数!
所以最小的 bi≠0b_i\neq 0bi=0 的 iii,就意味着 aaa 中原来有 bib_ibi 个 iii。
然后考虑第二小的 bj≠0b_j\neq 0bj=0 的 jjj,会注意到有可能 bib_ibi 个 iii 可能会组合出 jjj。
减去这些组合就是 aaa 中原本有 bj′b_j'bj′ 个 jjj。
发现这就是个背包 dpdpdp 的过程。
容量 mmm,但最多只会背包 nnn 次。
所以跑得很快。
solution
与我的想法相同。
每次找到子集中最小的元素,也就是最小的 bib_ibi 不等于 000 的 iii,然后从背包里删去即可。
删除就是可以理解成逆向执行一下背包中加入元素 xxx 的操作,也就是从小到大,执行 bi−=bi−xb_i-=b_{i-x}bi−=bi−x。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 55
#define maxm 10005
#define int long long
int n, m, cnt;
int b[maxm], a[maxn], f[maxm];
int c[maxn][maxn];signed main() {freopen( "subset.in", "r", stdin );freopen( "subset.out", "w", stdout );scanf( "%lld %lld", &n, &m );for( int i = 0;i <= n;i ++ ) {c[i][0] = c[i][i] = 1;for( int j = 1;j < i;j ++ )c[i][j] = c[i - 1][j - 1] + c[i - 1][j];}for( int i = 0;i <= m;i ++ ) scanf( "%lld", &b[i] );f[0] = 1;for( int i = 1;i <= m;i ++ ) {b[i] -= f[i];if( ! b[i] ) continue;for( int j = 1;j <= b[i];j ++ ) a[++ cnt] = i;for( int j = m;j;j -- ) {for( int k = 1;k <= b[i];k ++ )if( j < k * i ) break;else f[j] += f[j - k * i] * c[b[i]][k];}}for( int i = 1;i <= n;i ++ ) printf( "%lld ", a[i] );return 0;
}