P3067 Balanced Cow Subsets G

折半搜索好题

P3067 Balanced Cow Subsets G

思路:

1. 分析:

首先,由数据范围可以判断出,一定是搜索。

并且 \(n\le 20\) 告诉我们还可以用状压来记录状态。

然后注意,题目的要求是对于求有多少“平衡”的子集,所以我们可以有三种思路:

  1. 枚举每个集合,判断是否可以拆分。
  2. 枚举某个集合,判断是否有另一个集合可以拼在一起组合出一个答案。
  3. 整体枚举,对于每一个奶牛将其分入 \(A\)\(B\) 或不选。

显然,第一个无法快速的判断,第二个无法快速去重,第三个复杂度是 \(3^n = 3486784401\approx 3.4e9\) 无法接受。

但是对于前两个,显然不像可做的样子,考虑转化第三个。

2. 转化:

对于暴搜一类的算法的优化,要么是剪枝,要么是折半搜索。

而对于求方案数量的题目,一般不能剪枝,所以考虑折半搜索。

但是折半搜索要求搜索的内容必须独立,不会相互影响,还要求对于前后搜索出来的结果要可以快速合并,现在分为 \(A\)
\(B\) 两组显然不符合。

还要转化:

对于 \(A=B\) 一类的东西,有一个转化的通法:

\(A=B \rArr A-B=0\)

即在求分组之后相等的时候,可以转化为相减为 \(0\),方便求解。

就是可以对于需要分组的数分配权值\(1\)\(0\)\(-1\),直接累加,就代表“选”“不选”“放在另一组”,求的就是最后的累加和为 \(0\)

这样,我们就可以将每个物品独立出来,变成了每个物品有独立的 \(3\) 种状态,最后要求前后的累加和为 \(0\)

所以直接折半搜索,之后通过状压来去重。

Code:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int rt=0;	char g=getchar();
	while(g<'0'||g>'9')	g=getchar();
	while(g>='0'&&g<='9')	rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
	return rt;
}
int n,m,up,ans;
int a[25],ck[(1<<20)];
int cnt;
map<int,int>mp;
vector<int>v[(1<<20)];
inline void dfs1(int now,int sum,int w)
{
	if(now>up)
	{
		if(!mp.count(sum))	mp[sum]=++cnt;
		v[mp[sum]].push_back(w);
		return;
	}
	dfs1(now+1,sum,w);
	dfs1(now+1,sum-a[now],w|(1<<(now-1)));
	dfs1(now+1,sum+a[now],w|(1<<(now-1)));
}
inline void dfs2(int now,int sum,int w)
{
	if(now>up)
	{
		int opt=mp[-sum];
		for(int i=0;i<v[opt].size();i++)	ck[w|v[opt][i]]=1;
		return;
	}
	dfs2(now+1,sum,w);
	dfs2(now+1,sum-a[now],w|(1<<(now-1)));
	dfs2(now+1,sum+a[now],w|(1<<(now-1)));
}
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)	a[i]=read();
	if(n==1){putchar('0');return 0;}
	up=(n>>1);	dfs1(1,0,0);
	up=n;	dfs2((n>>1)+1,0,0);
	for(int i=1;i<(1<<n);i++)	ans+=ck[i];
	printf("%d",ans);
	return 0;
}
posted @ 2024-07-11 15:05  YT0104  阅读(3)  评论(0编辑  收藏  举报