2017年8月7日提高组T3 选数

66 篇文章 0 订阅

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值