>Link
luogu P3067
>Description
给n个数,从中任意选出一些数,使这些数能分成和相等的两组。
求有多少种选数的方案。
N ≤ 20 , a i ≤ 1 0 8 N\le 20, a_i \le 10^8 N≤20,ai≤108
>解题思路
考虑普通的爆搜,用一个 s u m sum sum表示当前的离两组相等的“差值”,每个数就有三种状态:
- 不选, s u m sum sum不变
- 选进第一组, s u m + a i sum+a_i sum+ai
- 选进第二组, s u m − a i sum-a_i sum−ai
最后 s u m = 0 sum=0 sum=0 ,说明两组的和是相等的
但是 O ( 3 20 ) O(3^{20}) O(320) 会T,所以上折半搜索
我们搜两遍,如果第一遍的 s u m sum sum加第二遍的 s u m sum sum为0的话就是一个合法的方案
因为题目要求的是选数的方案(选的数都是一样的,分组不同算同一种方案),所以我们还要带上当前选了哪些数的状态, N N N很小,我们可以用一个状压来保存
对于第二遍的结果 s u m 2 sum_2 sum2,我们要找 s u m 1 = − s u m 2 sum_1=-sum_2 sum1=−sum2 的所有状态,然后枚举一遍这些合法的状态,把两遍的状态合到一起在相应的位置标记一下,最后在扫一遍所有状态累计答案,这样可以避免重复的方案
所以我们就在每个 s u m sum sum建一个vector,把相同 s u m sum sum的状态都放进去
因为 s u m ≤ 20 ∗ 1 0 8 sum \le20*10^8 sum≤20∗108,直接每个开个vector空间肯定会爆,所以我们就给依次出现的 s u m sum sum标个号,防止开不会用到的vector
对 s u m sum sum标号,就用一个map来存(这样每个开个map不会爆)
有几个很坑的点:map不同于数组,每次调用费时会多!!!所以如果要重复调用map里的同一个值,就先把它拿个变量存,然后用变量!!!求vector的size也一样!!!
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#define N 30
#define LL long long
using namespace std;
map<int, int> id;
vector<int> q[2000000];
int n, tot, a[N];
LL ans;
bool ok[2000000];
void dfs1 (int now, int sum, int qq)
{
if (now > n / 2)
{
if (!qq) return;
if (!sum) ok[qq] = 1;
if (!id[sum]) id[sum] = ++tot;
q[id[sum]].push_back (qq);
return;
}
dfs1 (now + 1, sum, qq);
dfs1 (now + 1, sum + a[now], qq | (1 << (now - 1)));
dfs1 (now + 1, sum - a[now], qq | (1 << (now - 1)));
}
void dfs2 (int now, int sum, int qq)
{
// printf ("%d\n", qq);
if (now > n)
{
if (!qq) return;
if (!sum) ok[qq] = 1;
if (id[sum])
{
int len = q[id[sum]].size(), x = id[sum];
for (int i = 0; i < len; i++)
ok[qq | q[x][i]] = 1;
}
return;
}
dfs2 (now + 1, sum, qq);
dfs2 (now + 1, sum + a[now], qq | (1 << (now - 1)));
dfs2 (now + 1, sum - a[now], qq | (1 << (now - 1)));
}
int main()
{
scanf ("%d", &n);
for (int i = 1; i <= n; i++) scanf ("%d", &a[i]);
dfs1 (1, 0, 0);
dfs2 (n / 2 + 1, 0, 0);
for (int i = 1; i <= (1 << n); i++)
ans += ok[i];
printf ("%lld", ans);
return 0;
}