Description
给出n个数a[i],现在可以在其中任意选出若干个数,问有多少种选择方案,使得这几个数可以分成两个和相等的集合。
Input
第一行是一个正整数n,第二行每行n个正整数。
Output
输出一个数,表示方案数。
Hint
对于30%的数据,n<=10.
对于100%的数据,n<=20,a[i]<=100000000.
Source
BY BPM
Solution
略难
每个数字前可以填1、-1、0。那么分别代表选进1序列、2序列、不选。若要求两序列相等,则表示1、2序列之和为0
原序列拆开两半分别dfs,得出2个
310
大小的结果数组。排序后查找和为0的匹配数量。
特别地,单独的0可以作为一个结果考虑,两个0也可以作为结果考虑。而全不选的结果是不能计入答案的,因此ans-1
由于相同数字组成的只算一种方案,即1+4-2-3=3-1-4+2=0,所以我们需要判重。注意n并不总是偶数即可
Code
#include <stdio.h>
#include <algorithm>
#define rep(i, st, ed) for (int i = st; i <= ed; i += 1)
#define max(x, y) (x)>(y)?(x):(y)
#define abs(x) (x)<0?(-x):(x)
#define N 21
struct data{int x, id;}r[2][1 << N | 1];
int t[N], cnt[3] = {0, 0};
bool vis[1 << N | 1];
inline int cmp1(data a, data b){return a.x < b.x;}
inline int cmp2(data a, data b){return a.x > b.x;}
inline void dfs(int dep, int tot, int lim, int opt, int bn){
if (dep == lim + 1){
r[opt][++ cnt[opt]].x = tot;
r[opt][cnt[opt]].id = bn;
// printf("%d %d\n", tot, bn);
return;
}
dfs(dep + 1, tot + t[dep], lim, opt, bn << 1 | 1);
dfs(dep + 1, tot - t[dep], lim, opt, bn << 1 | 1);
dfs(dep + 1, tot, lim, opt, bn << 1);
}
int main(void){
int n;
scanf("%d", &n);
rep(i, 1, n){
scanf("%d", &t[i]);
}
dfs(1, 0, n / 2, 0, 0);
dfs(n / 2 + 1, 0, n, 1, 0);
std:: sort(r[0] + 1, r[0] + cnt[0] + 1, cmp1);
std:: sort(r[1] + 1, r[1] + cnt[1] + 1, cmp2);
int ans = 0;
int j = 1;
rep(i, 1, cnt[0]){
while (r[0][i].x + r[1][j].x > 0 && j <= cnt[1]){j += 1;}
int k = j;
while (r[0][i].x + r[1][k].x == 0 && k <= cnt[1]){
if (!vis[(r[0][i].id << (n - n / 2)) | r[1][k].id]){
vis[(r[0][i].id << (n - n / 2)) | r[1][k].id] = 1;
ans += 1;
}
k += 1;
}
}
printf("%d\n", ans - 1);
return 0;
}