n<=20个数,求能分成两个和相同的子集的子集数。
枚举子集的子集,复杂度3^n,不可,考虑折半。在一种可行方案中,每个数的系数只会是0,-1,1,题目就是要求找和为0的两个子集拼起来,将其中一个子集取反就对应成两个值相同的方案。比如找到一个子集值为x,那么另一个子集的值应为-x,只要把-x这个子集的系数全部取反,就得到两个值相同的集合,对应一种方案。
这样转换方便折半搜索的合并过程。在合并时,枚举左边的集合,把该集合对应的状态加进数组里排序,预先把右边所有状态排序后,就可以线性时间内比较相同。若是没有转换,则需要找和为0的两个状态,麻烦!复杂度6^(n/2)。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<algorithm> 5 //#include<iostream> 6 using namespace std; 7 8 int n,half; 9 #define maxn 100011 10 int first[maxn],list[maxn],next[maxn],cntl=0; 11 struct Right 12 { 13 int v,S; 14 bool operator < (const Right &b) const {return v<b.v;} 15 }r[maxn];int cntr=0; 16 bool vis[1100011]; 17 int a[23]; 18 int tmp[maxn],lt=0; 19 void dfsl(int p,int v,int S) 20 { 21 if (p==half+1) 22 { 23 list[++cntl]=v; 24 next[cntl]=first[S]; 25 first[S]=cntl; 26 return; 27 } 28 dfsl(p+1,v,S); 29 dfsl(p+1,v+a[p],S|(1<<(p-1))); 30 dfsl(p+1,v-a[p],S|(1<<(p-1))); 31 } 32 void dfsr(int p,int v,int S) 33 { 34 if (p==n+1) 35 { 36 r[++cntr].v=v; 37 r[cntr].S=S; 38 return; 39 } 40 dfsr(p+1,v,S); 41 dfsr(p+1,v+a[p],S|(1<<(p-1))); 42 dfsr(p+1,v-a[p],S|(1<<(p-1))); 43 } 44 int main() 45 { 46 scanf("%d",&n);half=n/2; 47 for (int i=1;i<=n;i++) scanf("%d",&a[i]); 48 memset(first,0,sizeof(first)); 49 dfsl(1,0,0);dfsr(half+1,0,0); 50 sort(r+1,r+1+cntr); 51 memset(vis,0,sizeof(vis)); 52 for (int i=0;i<(1<<half);i++) 53 { 54 lt=0; 55 for (int j=first[i];j;j=next[j]) tmp[++lt]=list[j]; 56 sort(tmp+1,tmp+1+lt); 57 int k=1; 58 for (int j=1;j<=cntr;j++) 59 { 60 while (k<=lt && tmp[k]<r[j].v) k++; 61 if (k==lt+1) break; 62 if (tmp[k]==r[j].v) vis[i|r[j].S]=1; 63 } 64 } 65 int ans=0; 66 for (int i=1;i<(1<<n);i++) if (vis[i]) ans++; 67 printf("%d ",ans); 68 return 0; 69 }
注意不要把0算进去。。。。。。