USACO2011Open Gold Balanced Cow Subsets

N的范围很小,可以联想到枚举子集和状压.但是如果直接枚举两个子集,显然是不够的.那么我们可以联想到折半枚举!Meet inthe Middle!

    把n个数分成两部分A,B集合,答案子集的来源有以下几种:

1.    A集合的子集.

2.   B集合的子集.

3.   一部分是A的子集,一部分是B的子集.

对于1,2两种情况都可以直接预处理出:

枚举A的子集x,再枚举x的子集y,把x分为y,和x^y两个集合,判断它们的总和是否相等.

对于第3种情况:

         假设a,b在A集合里,c,d在B集合里,而有a+c=b+d.我们可以把式子移项=>a-b=d-c,也就是说在A集合(集合元素为n1)中的每个元素有三种可能:不取,给A集合,给B集合.那么对于A集合一共有3n1种方案.同理B集合就有3n2种方案,把各集合产生的方案的,与选取的元素记录下来,按照排序.对于两个有序数组进行归并:

对于值相等的两个集合,它们的交集一定可以满足条件.

优化:

     在归并的过程中,会发现有很多重复的情况.

     在A集合里既有a-b的状态,也有b-a的状态.它们分别于d-c,c-d对应,但是它们产生的交集是相同的,都是{a,b,c,d}.所以b-a和c-d状态是无效的,我们在处理状态时可以过滤掉 值为负数的状态.

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
const int M=20;
int A[M],mark[1<<M],n,f[1<<M],totc=0,totb=0;
struct node{
    int val,p;
}B[60005],C[60005];
bool cmp(node a,node b){
    return a.val<b.val;
}
int solve(int st,int t){//判断一个集合内部满足要求的子集个数
    int i,j,k,ans=0;
    for(i=1;i<(1<<t);i++){
        f[i]=0;
        for(j=0;j<t;j++)
            if(i&(1<<j))f[i]+=A[st+j];
        for(j=i;j;j=(j-1)&i){
            int a=j,b=i^j;//两个子集
            if(a==0||b==0)continue;
            if(f[a]==f[b]){
                ans++;break;//
            }
        }
    }
    return ans;
}
void get(int all,int l,int r,node t[],int &num){
    int i,j,k;
    for(i=all;i;i=(i-1)&all){
        for(j=i;j;j=(j-1)&i){
            int a=j,b=i^j,now=0;//b集合要给对方 
            for(k=l;k<r;k++){
                if(a&(1<<k))now+=A[k];
                else if(b&(1<<k))now-=A[k];
            }
            if(now>=0)t[++num]=(node){now,i};
        }
    }
    sort(t+1,t+1+num,cmp);
}
int main(){
    int i,j,k,ans=0,stb=1,stc=1;
    scanf("%d",&n);
    for(i=0;i<n;i++)scanf("%d",&A[i]);
    int s1=n/2,s2=n-n/2;
    ans+=solve(0,s1)+solve(s1,s2);
    get((1<<s1)-1,0,s1,B,totb);
    get(((1<<n)-1)^((1<<s1)-1),s1,n,C,totc);
    while(stb<=totb&&stc<=totc){//归并两个序列
        while(stc<=totc&&stb<=totb&&C[stc].val!=B[stb].val){
            while(C[stc].val<B[stb].val)stc++;
            while(B[stb].val<C[stc].val)stb++;
        }
        if(stc>totc||stb>totb)break;
        int c1=stc,b1=stb,x=C[stc].val;
        while(C[stc].val==x)stc++;
        while(B[stb].val==x)stb++;
        for(i=c1;i<stc;i++)
            for(j=b1;j<stb;j++)
                mark[C[i].p|B[j].p]=1;
    }
    for(i=0;i<(1<<n);i++)ans+=mark[i];
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值