大致题意: 从(n)个数中选出若干个数(不能不选),问有多少种选数的方案,使得选出的数能被分为两个和相等的集合。
前言
一眼就想到了这个做法,感觉理论上应该很难卡,可复杂度还是不太会证。。。
(Meet in Middle)
比较显然,每个数有三种状态:不选、被分在第一个集合(加上权值)、被分在第二个集合(减去权值)。
然后考虑折半搜索(即(Meet in Middle)),就可以在(O(3^{10}))的复杂度内分别搜出数列两部分各自的状态。而如果两个状态能够匹配,当且仅当两个状态的权值互为相反数。
但若仅仅是这么搞,就会发现同一个集合因不同的划分方式被计算了多次。
因此对于每个状态我们不仅要记下值的和,还要状压记下集合内元素的选择方式。
然后我们枚举权值互为相反数的每一对状态(就是这里,我感觉如果同一权值有很多状态会不会被卡掉啊),判断这种集合方式是否被选择过,没选择过则标记选择,并将答案加(1)。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
using namespace std;
int n,t1,t2,a[N+5],vis[1<<N+1];
struct data
{
int v,s;I data(CI a=0,CI b=0):v(a),s(b){}
I bool operator < (Con data& o) Con {return v<o.v;}
}s1[60000],s2[60000];
I void dfs(CI l,CI r,CI v=0,CI s=0)//Meet in Middle
{
if(l>r) return (void)(r^n?s1[++t1]=data(v,s):s2[++t2]=data(-v,s));//存下状态,注意第二种权值和取相反数(后来想想其实不用)
dfs(l+1,r,v,s),dfs(l+1,r,v+a[l],s|(1<<l-1)),dfs(l+1,r,v-a[l],s|(1<<l-1));//分三种状态
}
int main()
{
RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
dfs(1,n/2),dfs(n/2+1,n),sort(s1+1,s1+t1+1),sort(s2+1,s2+t2+1);//排序
RI ans=0,p,l,r;for(p=1,l=1;l<=t1;l=r+1)
{
r=l;W(r<=t1&&s1[l].v==s1[r+1].v) ++r;//同种权值一并处理
W(p<=t2&&s2[p].v<s1[l].v) ++p;W(p<=t2&&s2[p].v==s1[l].v)//找到对应的权值
{for(i=l;i<=r;++i) !vis[s1[i].s|s2[p].s]&&(vis[s1[i].s|s2[p].s]=1,++ans);++p;}//注意集合方式要不同
}return printf("%d",ans-1),0;//答案减1,因为全不选的情况会被计算在内
}