题目大意:
给出N个数字(N<=20),需要求出有多少个集合可以被分成两个权值相等的集合
题目思路:
感谢加乐送来的难题
考虑折半搜索就可以了
折半搜索的分治与分治类似:
1.将整体集合分为两个集合A,B
2.考虑A集合内部的贡献
3.考虑B集合内部的贡献
4.考虑A与B联合内部的贡献
对于这个题来说,考虑每个物品的三种状态,假设枚举的能否分成两个相等的集合X,Y
对于每个物品来言:
1.要么放进X集合
2.要么放进Y集合
令放进Y集合是负数,X集合是正数,那么只需要知道有多少个权值是0的状态(二进制状压)即可
对于两个集合合并 只需要找到 X1-Y1 = Y2 - X2 也就是说 X1 + X2 + Y1 + X2 = 0
枚举一下 两个A B两个集合联合的贡献即可
其实正解给出了双指针,因为要找出有哪些点,其实双指针只能优化一部分,极限数据还是要n^2去跑一下匹配
Code:
/*** keep hungry and calm CoolGuang!***/
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define d(x) printf("%lld\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17;
const ll maxn = 2e5+700;
const ll mod= 1e8+7;
template<typename T>inline void read(T &a){char c=getchar();T x=0,f=1;while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;
}ll n,m,p;
struct node{
int x,sta;
bool friend operator<(node a,node b){
return a.x < b.x;
}
}a[maxn*20],b[maxn*20];
int num[maxn];
int cnta = 0,cntb = 0;
int vis[maxn*20];
inline void dfs(register int s,register int e,register int f,register int sum,register int sta){
if(s == e+1){
if(sum == 0) vis[sta] = 1;
if(f) a[++cnta] = node{sum,sta};
else b[++cntb] = node{sum,sta};
return ;
}
dfs(s+1,e,f,sum,sta);
dfs(s+1,e,f,sum+num[s],sta|(1<<(s-1)));
dfs(s+1,e,f,sum-num[s],sta|(1<<(s-1)));
}
int main(){
read(n);
for(register int i=1;i<=n;i++) read(num[i]);
dfs(1,n/2,1,0,0);
dfs(n/2+1,n,0,0,0);
sort(a+1,a+1+cnta);
sort(b+1,b+1+cntb);
register int sa = 1,sb = cntb;
while(sa<=cnta && sb>=1){
while(sb>=1&&a[sa].x+b[sb].x>0) sb--;
register int pos = sb;
while(sb>=1 && a[sa].x + b[sb].x == 0){
vis[ a[sa].sta | b[sb].sta ] = 1;
sb--;
}
if(sa<cnta && a[sa+1].x == a[sa].x ) sb = pos;
sa++;
}
register int ans = 0;
for(register int i=1;i<(1<<n);i++)
if(vis[i])
ans++;
printf("%d\n",ans);
return 0;
}
/**
**/