可采用bitset进行优化, 解释的比较随意, 望大佬补充
1. 使用dp数组
集合表示:
$f[i][j]$ 表示从 $1\sim i$ 中选砝码, 是否能使得砝码能量出为 $j$ 的物品, (集合的属性为bool类型的是否)
状态计算:
-
不选第 $i$ 个物品, $f[i][j]=(f[i-1][j]==1)$
(即如果选前 $i-1$ 个物品能凑出 $j$ , 那么选前 $i$ 个物品一定能凑出 $j$ ) -
如果选第 $i$ 个物品, $f[i][j]=(f[i][j]\;\|\;f[i-1][j+w]\;\|\;f[i-1][abs(j-w)])$
(选第 $i$ 个物品时, 可以选择使得原有砝码总重加上 $w_i$, 也可以选择使得原有砝码总重减去 $w_i$ , 如果 $j-w_i$ 或 $j+w_i$ 在之前被凑出来了, 那 $j$ 即可被凑出来, 状态数组为 true)
C++ 代码
const int N=110, M=2e5+10; //数组要开两倍, 否则计算 f[i-1][j+w]时可能会越界
void solve() {
cin>>n;
int sum=0;
for(int i=1; i<=n; i++)
cin>>a[i],
sum+=a[i];
f[0][0]=1;
for(int i=1; i<=n; i++)
for(int j=0; j<=sum; j++)
f[i][j]=f[i-1][j]||f[i-1][j+a[i]]||f[i-1][abs(j-a[i])];
int ans=0;
for(int i=1; i<=sum; i++)
ans+=f[n][i];
cout<<ans;
}
2. 使用bitset优化
使用01串二进制表示每一个重量是否存在
例如对于样例 1, 2, 4, 我们进行枚举:
由于砝码总和为7, 所以我们至少需要8位01串 s 来表示能凑出的重量
先只考虑砝码相加
1. 初始时所有数值都不选, 能凑出重量0, s="00000001"
2. 选重量1后, 能凑出新的重量是: 所有原来可凑出重量+1,
新凑出重量 s1="00000010"
, 原来凑出重量 ss="00000001"
, 所有能凑出的重量: s="00000011"
3. 选重量2后, 能凑出新的重量是: 所有原来可凑出重量+2,
新凑出重量 s2="00001100"
, 原来凑出重量 ss="00000011"
, 所有能凑出的重量: s="00001111"
4. 选重量4后, 能凑出新的重量是: 所有原来可凑出重量+4,
新凑出重量 s3="11110000"
, 原来凑出重量 ss="00001111"
, 所有能凑出的重量: s="11111111"
5. 观察上述变化我们可以发现, 每多选一个重量 x , 所有可凑出重量 s 的变化是: s|=s<<x
, (s<<x
相当于通过原始重量和重量 x 能凑出来的新的重量, 再 s|=s<<x
相当于将新增方案累加到原始方案上)
然后考虑砝码间相减的情况
同理, 相减有 s|=s>>x
,
对于这种合法性, 我们可以这样理解:
>>
操作相当于在原来所有可凑出重量上减去x, 看是否会出现新的重量, 可以保证这种一定不重复
对于方案1: a+b+c, 减去 a 后得到 b+c, 其早已经被累计在所有合法方案s中, |
操作不会使得方案重复
对于方案2: a+b+c, 减去 d 后得到 e, 这是新的方案, |
操作可以累加至全局合法方案s中
C++ 代码
void solve() {
int n; cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
s[0]=1; //初始化
for(int i=1; i<=n; i++)
s|=s<<a[i];
for(int i=1; i<=n; i++)
s|=s>>a[i];
//统计1的个数
cout<<s.count()-1; //最后要减掉初始化的那个1,因为不存在凑出0的情况
}