P3067 Balanced Cow Subsets G
折半搜索好题
P3067 Balanced Cow Subsets G
思路:
1. 分析:
首先,由数据范围可以判断出,一定是搜索。
并且 \(n\le 20\) 告诉我们还可以用状压来记录状态。
然后注意,题目的要求是对于求有多少“平衡”的子集,所以我们可以有三种思路:
- 枚举每个集合,判断是否可以拆分。
- 枚举某个集合,判断是否有另一个集合可以拼在一起组合出一个答案。
- 整体枚举,对于每一个奶牛将其分入 \(A\),\(B\) 或不选。
显然,第一个无法快速的判断,第二个无法快速去重,第三个复杂度是 \(3^n = 3486784401\approx 3.4e9\) 无法接受。
但是对于前两个,显然不像可做的样子,考虑转化第三个。
2. 转化:
对于暴搜一类的算法的优化,要么是剪枝,要么是折半搜索。
而对于求方案数量的题目,一般不能剪枝,所以考虑折半搜索。
但是折半搜索要求搜索的内容必须独立,不会相互影响,还要求对于前后搜索出来的结果要可以快速合并,现在分为 \(A\)、
\(B\) 两组显然不符合。
还要转化:
对于 \(A=B\) 一类的东西,有一个转化的通法:
\(A=B \rArr A-B=0\)
即在求分组之后相等的时候,可以转化为相减为 \(0\),方便求解。
就是可以对于需要分组的数分配权值\(1\)、\(0\)或\(-1\),直接累加,就代表“选”“不选”“放在另一组”,求的就是最后的累加和为 \(0\)。
这样,我们就可以将每个物品独立出来,变成了每个物品有独立的 \(3\) 种状态,最后要求前后的累加和为 \(0\)。
所以直接折半搜索,之后通过状压来去重。
Code:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int rt=0; char g=getchar();
while(g<'0'||g>'9') g=getchar();
while(g>='0'&&g<='9') rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
return rt;
}
int n,m,up,ans;
int a[25],ck[(1<<20)];
int cnt;
map<int,int>mp;
vector<int>v[(1<<20)];
inline void dfs1(int now,int sum,int w)
{
if(now>up)
{
if(!mp.count(sum)) mp[sum]=++cnt;
v[mp[sum]].push_back(w);
return;
}
dfs1(now+1,sum,w);
dfs1(now+1,sum-a[now],w|(1<<(now-1)));
dfs1(now+1,sum+a[now],w|(1<<(now-1)));
}
inline void dfs2(int now,int sum,int w)
{
if(now>up)
{
int opt=mp[-sum];
for(int i=0;i<v[opt].size();i++) ck[w|v[opt][i]]=1;
return;
}
dfs2(now+1,sum,w);
dfs2(now+1,sum-a[now],w|(1<<(now-1)));
dfs2(now+1,sum+a[now],w|(1<<(now-1)));
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();
for(int i=1;i<=n;i++) a[i]=read();
if(n==1){putchar('0');return 0;}
up=(n>>1); dfs1(1,0,0);
up=n; dfs2((n>>1)+1,0,0);
for(int i=1;i<(1<<n);i++) ans+=ck[i];
printf("%d",ans);
return 0;
}