您的位置:首页 > 其它

【NOIP2016提高A组集训第4场11.1】平衡的子集

2016-11-02 17:31 323 查看

Description

夏令营有N个人,每个人的力气为M(i)。请大家从这N个人中选出若干人,如果这些人可以分成两组且两组力气之和完全相等,则称为一个合法的选法,问有多少种合法的选法?

Solution

看到范围只有20,我已开始就打暴力,我以为可以快速判断是否合法,但是失败了。

正解超级机智。

中途相遇法:首先把这20个数拆成两个数集,然后每个点有三个值-1,0,1,那么很明显就是-1就把这个数放到另一边,1就是放到这边,0表示不选。

那么暴力去搞这两个数集,只用O(2∗310)。

然后把这两个数组,按1、-1这些取出来的值排一次序,因为还要判重,所以每个选的情况还要打个二进制标记,然后两个指针同时去扫,就可以了。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define rep(i,a) for(i=first[a];i;i=next[i])
using namespace std;
const int maxn=30,mo=1100007;
int i,j,k,l,t,n,m,ans,xiao,da,r,num,op;
int a[maxn],er[maxn];
int p[mo*2];
int first[mo*2],last[mo*2],next[mo*2],hou[mo*2],tou[mo*2];
bool bz[2025][2025];
int hash(int x){
int y=(x+mo*1000)%mo;
while(p[y]!=op&&p[y]!=x)y=(y+1)%mo;
return y;
}
void dfs(int x,int y,int z){
if(x==n/2+1){
k=hash(y);
if(p[k]>=0){
t=hou[k];
next[t]=++num;
last[num]=z;
hou[k]=num;
}
else p[k]=y,hou[k]=++num,last[num]=z,tou[k]=num;
return;
}
dfs(x+1,y,z);
dfs(x+1,y+a[x],z+er[x-1]);
dfs(x+1,y-a[x],z+er[x-1]);
}
void dfs1(int x,int y,int z){
if(x==n+1){
k=hash(y);
if(p[k]==op)return;
for(i=tou[k];i;i=next[i]){
if(!bz[z][last[i]]){
bz[z][last[i]]=1;
ans++;
}
}
return;
}
dfs1(x+1,y,z);
dfs1(x+1,y+a[x],z+er[x-n/2-1]);
dfs1(x+1,y-a[x],z+er[x-n/2-1]);
}
int main(){
freopen("subset.in","r",stdin);
freopen("subset.out","w",stdout);
//   freopen("fan.in","r",stdin);
scanf("%d",&n);
fo(i,1,n)scanf("%d",&a[i]);
memset(p,128,sizeof(p));
op=p[0];
er[0]=1;
fo(i,1,10)er[i]=er[i-1]*2;
dfs(1,0,0);
dfs1(n/2+1,0,0);
ans--;
printf("%d\n",ans);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: