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;
}

發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章