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);
}

posted @ 2017-10-30 17:00  扩展的灰(Extended_Ash)  阅读(101)  评论(0编辑  收藏  举报