Jzoj4841 平衡的子集
夏令营有N个人,每个人的力气为M(i)。请大家从这N个人中选出若干人,如果这些人可以分成两组且两组力气之和完全相等,则称为一个合法的选法,问有多少种合法的选法?
这道题我们拷打数据范围后知道应该是折半搜索了
但是似乎不是那么好做
我们考虑如何搜索,显然每个人有三种状态,在队伍1中,在队伍2中和没有被选入
我们将所有可能的状态都搜索出来加入一个数据结构(按照队伍1-队伍2的力量之差p作为关键字)
但是这样做是有问题的,很多情况下会重复计算
例如,四个人M为1 1 1 1,折半搜索以后,我们会得到以下几个状态{-2,-1,0,1,2},其中有两种可能的情况
1.两种状态所选人物完全相同但因为分配方法不同导致p不相同
2.两种状态所选人物不同但是因为分配方法导致p相同
所以我们不能简单记录选了哪些人或者是这些人的p是多少而是两者都要计算
所以我们可以考虑用map套set(一个p和一个k对应了一种选人的方法)
map的第一维表示的是p,每个节点都是一个set存储一个二进制的选人方案k(1表示选择,0表示不选)
这样的话在第二次搜索时,我们将值为-p的那个set拿出来,遍历其中每一个节点,在全局开一个数组vs表示某种选人的方式是否合法
最后统计答案即可
注:本题正解应为hash表
#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<set>
#include<map>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
map<int,set<int> > s;
set<int> vis[1024],q;
int n,v[30],m,ans=0;
bool vs[1<<20];
void dfs(int x,int p,int k){
if(x>m) s[p].insert(k);
else {
dfs(x+1,p,k);//x不选
dfs(x+1,p+v[x],k+(1<<x-1));//x加入队伍1
dfs(x+1,p-v[x],k+(1<<x-1));//x加入队伍2
}
}
void dgs(int x,int p,int k){
if(x>n){
if(!vis[k].count(p)&&s.count(-p)){ //判重,对于k和p相等的情况可以不用重复计算
q=s[-p];
for(set<int>::iterator it=q.begin();it!=q.end();++it) vs[*it+(k<<10)]=1; //遍历set统计答案
vis[k].insert(p);
return;
}
return;
} else {
dgs(x+1,p,k);
dgs(x+1,p+v[x],k+(1<<x-m-1));
dgs(x+1,p-v[x],k+(1<<x-m-1));
}
}
int main(){
freopen("subset.in","r",stdin);
freopen("subset.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",v+i);
m=n>>1;
dfs(1,0,0);
dgs(m+1,0,0);
for(int i=1;i<(1<<20);++i) ans+=vs[i];
printf("%d\n",ans);
}