题目:
有多少个非空子集,能划分成和相等的两份。
solution:
只想到了
3
n
3^n
3n暴力
其实可以用
m
e
e
t
i
n
t
t
h
e
m
i
d
d
l
e
meet\ int\ the\ middle
meet int the middle的思想降低复杂度
左边的那些
3
N
/
2
3^{N/2}
3N/2枚举分别是不放还是放到第一组还是放到第二组,并记录下来。
右边的
3
N
/
2
3^{N/2}
3N/2枚举后,再
2
N
/
2
2^{N/2}
2N/2看看左边符合这个值的那些,就行了。
总复杂度
O
(
6
N
/
2
)
O(6^{N/2})
O(6N/2)。
其实其他的都能想到,但就是降低复杂度的方法以前没有用过
m
e
e
t
i
n
t
h
e
m
i
d
d
l
e
meet\ in\ the\ middle
meet in the middle只做过裸题,其实很多时候都可以这样做
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#define maxn 25
using namespace std;
int n,a[maxn],ans,haf,tot;
bool can[(1<<20)+5];
map<int,int> mp;
set<int> s[60000];
set<int>::iterator it;
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
inline void solve(int ss,int lfsm,int rgsm){
if(!mp.count(rgsm-lfsm)) return;
int cur=mp[rgsm-lfsm];
for(it=s[cur].begin();it!=s[cur].end();it++)
can[ss|(*it)]=true;
return;
}
void dfs(int now,int t,int cur,int lfsm,int rgsm){
if(now==t){
if(t==n) {solve(cur,lfsm,rgsm);return;}
int cc=lfsm-rgsm;
if(!mp.count(cc)) mp[cc]=++tot;
s[mp[cc]].insert(cur);
return;
}
dfs(now+1,t,cur,lfsm,rgsm);//不放
dfs(now+1,t,cur|(1<<now),lfsm+a[now+1],rgsm);//放左边
dfs(now+1,t,cur|(1<<now),lfsm,rgsm+a[now+1]);//放右边
return;
}
int main(){
freopen("subsets.in","r",stdin);
freopen("subsets.out","w",stdout);
n=rd();
for(int i=1;i<=n;i++) a[i]=rd();
haf=n/2;
dfs(0,haf,0,0,0);
dfs(haf,n,0,0,0);
for(int i=1;i<(1<<n);i++)
if(can[i]) ++ans;
printf("%d\n",ans);
return 0;
}