把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ2679】[USACO2012 Open] Balanced Cow Subsets(Meet in Middle)

点此看题面

大致题意:\(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,因为全不选的情况会被计算在内
}
posted @ 2020-05-14 11:02  TheLostWeak  阅读(126)  评论(0编辑  收藏  举报