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

Description

给出n个数a[i],现在可以在其中任意选出若干个数,问有多少种选择方案,使得这几个数可以分成两个和相等的集合。

Input

第一行是一个正整数n,第二行每行n个正整数。

Output

输出一个数,表示方案数。

Sample Input

4
1 2 3 4

Sample Output

3

Hint

对于30%的数据,n<=10.
对于100%的数据,n<=20,a[i]<=100000000.

分析:对于一个数列,设为Ai。我们可以在它的前面加一个系数Xi,Xi={-1,0,1},使得
x1a1+x2a2+x3a3+……+xnan=0

此时即为一组解。
这为什么是对的呢?!

-1和1指我选这个元素,0指不选这个数。而我们要把选的数分为和相等的两部分,-1指分到A集合,1指分到B集合。既然和为0,则可以说明两边的值是一样的。

厉害!!!!

我们可以对半搜索,把前N/2个和剩下的数分开枚举。记录下所有可以得到的值,一共有3^10个(当n=20时)。那如果对于前n/2个数存在x,另外的数存在-x,显然是1种解。只要把上下匹配即可。

我们发现,当序列1的数字增大时,序列二的对应查找数字减少。我们可以先把两个序列排序,一个从小到大,一个从大到小,也可以都从小到大,一个从后面开始搜,用两个指针,逐一判断。

另外,对于有的集合只存在与一边,那么我们另一边存在X={000000……}这个状态,所以无需特判,最后要减去两边全是0的情况。

第二是去重,比如对于样例:
1*1-1*2-1*3+1*4=0

-1*1+1*2+1*3-1*4=0
但其实是一种情况。
对于这种情况,我们可以这样理解。我们用一个二进制数代表集合内的选择情况,若xi=0,则二进制第i位等于0,否则都等于1。此时,对于以上两种情况,都是1111,开一个2^20的桶判断是否前面有出现即可。

代码:

type
 arr=array [0..60000] of longint;
var
 n,i,j,mid,e,d,ans,q,k:longint;
 s,t,x,y:arr;
 a:array [1..20] of longint;
 dep:array [0..1024*1024] of boolean;
procedure qsort(l,r:longint; var b,c:arr);
  var
    i,j,key,temp:longint;
  begin
    if l>=r then exit;
    i:=l;j:=r;
    key:=b[l+random(r-l+1)];
    repeat
      while  (b[i]<key) do inc(i);
      while  (b[j]>key) do dec(j);
      if i<=j then
      begin
        temp:=b[i];b[i]:=b[j];b[j]:=temp;
        temp:=c[i];c[i]:=c[j];c[j]:=temp;
        inc(i);dec(j);
      end;
    until i>j;
    qsort(l,j,b,c);
    qsort(i,r,b,c);
  end;


begin
 readln(n);
 for i:=1 to n do
  read(a[i]);
 mid:=n shr 1;
 x[1]:=0; x[2]:=a[1]; x[3]:=-a[1];
 s[1]:=0; s[2]:=1; s[3]:=1;
 e:=3;
 for i:=2 to mid do
  begin
   for j:=1 to e do
    s[j]:=s[j]*2;
   for j:=e+1 to 2*e do
    begin
     x[j]:=x[j-e]+a[i];
     s[j]:=s[j-e]+1;
    end;
   for j:=2*e+1 to 3*e do
    begin
     x[j]:=x[j-2*e]-a[i];
     s[j]:=s[j-2*e]+1;
    end;
   e:=e*3;
  end;
 y[1]:=0; y[2]:=a[mid+1]; y[3]:=-a[mid+1];
 t[1]:=0; t[2]:=1; t[3]:=1;
 d:=3;
 for i:=mid+2 to n do
  begin
   for j:=1 to e do
    t[j]:=t[j]*2;
   for j:=d+1 to 2*d do
    begin
     y[j]:=y[j-d]+a[i];
     t[j]:=t[j-d]+1;
    end;
   for j:=2*d+1 to 3*d do
    begin
     y[j]:=y[j-2*d]-a[i];
     t[j]:=t[j-2*d]+1;
    end;
   d:=d*3;
  end;
 qsort(1,e,x,s);
 qsort(1,d,y,t);
 j:=d;

 for i:=1 to e do
  begin
   while (y[j]+x[i]>0) and (j>=1) do dec(j);
   k:=j;
   while (y[k]+x[i]=0) and (k>=1) do
    begin
     q:=s[i] shl (n-mid)+t[k];
     if dep[q]=false then  begin ans:=ans+1; dep[q]:=true; end;
     dec(k);
    end;
  end;
 writeln(ans-1);
end.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值