P3067 [USACO12OPEN] Balanced Cow Subsets G
简要题意
给定 \(n\) 个物品,每个物品有权值 \(a\)。
对于整个物品集合 \(S\),询问有多少个非空子集使得该自己可以被分为两个集合,且两个集合中的物品权值和相等。
数据范围:\(n \le 20\)。
分析
转化一下题意:每个物品有“选且放到集合一中“,“选且放到集合二中”,”不选“这三种情况。
但这样总状态数是 \(O(3^n)\) 的,不可做。
但是我们可以将物品分成两部分,每一部分单独做,这样总状态数就是 \(O(3^{n/2})\) 的,可以接受。
考虑怎么统计答案。用桶记录每一种方案中集合一中物品总权值和集合二物品总权值的差,然后相乘即可。
这种方法的本质是通过合理的统计答案方式缩小问题规模。
代码
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Inf (1ll<<60)
#define For(i,s,t) for(int i=s;i<=t;++i)
#define Down(i,s,t) for(int i=s;i>=t;--i)
#define ls (i<<1)
#define rs (i<<1|1)
#define bmod(x) ((x)>=mod?(x)-mod:(x))
#define lowbit(x) ((x)&(-(x)))
#define End {printf("NO\n");exit(0);}
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
inline void ckmx(int &x,int y){x=(x>y)?x:y;}
inline void ckmn(int &x,int y){x=(x<y)?x:y;}
inline void ckmx(ll &x,ll y){x=(x>y)?x:y;}
inline void ckmn(ll &x,ll y){x=(x<y)?x:y;}
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline ll min(ll x,ll y){return x<y?x:y;}
inline ll max(ll x,ll y){return x>y?x:y;}
inline int read(){
register int x=0,f=1;
char c=getchar();
while(c<'0' || '9'<c) f=(c=='-')?-1:1,c=getchar();
while('0'<=c && c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x*f;
}
void write(int x){
if(x>=10) write(x/10);
putchar(x%10+'0');
}
const int N=25,M=(1<<20)+100;
int n,a[N],mid,cnt,nw,p[M];
ll ans;
map<int,int> rk;
vector<int> vis[M];
void dfs1(int u,int tot){
if(u==mid+1){
if(!rk[tot]) rk[tot]=++cnt;
vis[rk[tot]].push_back(nw);
//printf("1 %d\n",tot);
return;
}
nw<<=1;
dfs1(u+1,tot);
nw|=1;
dfs1(u+1,tot+a[u]);
dfs1(u+1,tot-a[u]);
nw>>=1;
}
void dfs2(int u,int tot){
if(u==n+1){
int id=rk[tot];
if(!id) return;
for(int v:vis[id])
p[v|(nw<<mid)]=1;
//printf("2 %d\n",tot);
return;
}
nw<<=1;
dfs2(u+1,tot);
nw|=1;
dfs2(u+1,tot+a[u]);
dfs2(u+1,tot-a[u]);
nw>>=1;
}
int main()
{
#if !ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
#endif
n=read();
For(i,1,n) a[i]=read();
mid=n>>1;
dfs1(1,0);
dfs2(mid+1,0);
For(i,1,(1<<n)-1) ans+=p[i];
printf("%lld",ans);
return 0;
}