本题无法通过动态规划予以解决。
由于本题中 N N N 最大为 40 40 40,生成所有子集显然不可行,需要适当变通。实际上,我们可以将数组分成两个数组 A A A 和 B B B,其大小只相差 1 1 1,分别生成 A A A 和 B B B 的所有子集和,由于 N N N 最大为 40 40 40,则 A A A 的大小最大为 20 20 20, A A A 中不同的子集和最多有 2 20 2^{20} 220 个,这是可行的。
对于数组 A A A 的每个子集和 s s s,在数组 B B B 的子集和中查找 T − s T - s T−s 是否存在,若存在,则将数组 A A A 中子集和 s s s 的数量 C s C_{s} Cs 乘以数组 B B B 中子集和 T − s T - s T−s 的数量 C T − s C_{T-s} CT−s,这就是对总方案数的贡献,逐个处理数组 A A A 的所有子集和累加即可,算法的时间复杂度为 O ( N × 2 N / 2 ) O(N \times 2^{N/2}) O(N×2N/2)。
可以使用
C++
\texttt{C++}
C++
STL
\texttt{STL}
STL 中的映射(
map
\texttt{map}
map)来存储子集和的个数。
需要注意,在解题过程中还需考虑子集不能为空。
// Subset Sum
// UVa ID: 12911
// Verdict: Accepted
// Submission Date: 2021-11-11
// UVa Run Time: 1.630s
//
// 版权所有(C)2021,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
int N, T, si[41];
map<long long, int> Ca, Cb;
int main(int argc, char *argv[])
{
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
while (cin >> N >> T)
{
for (int i = 0; i < N; i++) cin >> si[i];
int Na = N / 2;
if (Na == 0) Na++;
int Nb = N - Na;
Ca.clear();
for (int i = 1; i < (1 << Na); i++)
{
long long sum = 0;
for (int j = 0; j < Na; j++)
if (i & (1 << j))
sum += si[j];
Ca[sum]++;
}
Cb.clear();
for (int i = 1; i < (1 << Nb); i++)
{
long long sum = 0;
for (int j = 0; j < Nb; j++)
if (i & (1 << j))
sum += si[Na + j];
Cb[sum]++;
}
long long cnt = 0;
for (auto p : Ca)
if (Cb.count(T - p.first))
cnt += 1LL * p.second * Cb[T - p.first];
cout << cnt + Ca[T] + Cb[T] << '\n';
}
return 0;
}