Balanced Cow Subsets G【折半搜索】【状压】

>Link

luogu P3067


>Description

给n个数,从中任意选出一些数,使这些数能分成和相等的两组。
求有多少种选数的方案。

N ≤ 20 , a i ≤ 1 0 8 N\le 20, a_i \le 10^8 N20,ai108


>解题思路

考虑普通的爆搜,用一个 s u m sum sum表示当前的离两组相等的“差值”,每个数就有三种状态:

  1. 不选, s u m sum sum不变
  2. 选进第一组, s u m + a i sum+a_i sum+ai
  3. 选进第二组, s u m − a i sum-a_i sumai

最后 s u m = 0 sum=0 sum=0 ,说明两组的和是相等的

但是 O ( 3 20 ) O(3^{20}) O(320) 会T,所以上折半搜索
我们搜两遍,如果第一遍的 s u m sum sum加第二遍的 s u m sum sum为0的话就是一个合法的方案
因为题目要求的是选数的方案(选的数都是一样的,分组不同算同一种方案),所以我们还要带上当前选了哪些数的状态, N N N很小,我们可以用一个状压来保存

对于第二遍的结果 s u m 2 sum_2 sum2,我们要找 s u m 1 = − s u m 2 sum_1=-sum_2 sum1=sum2 的所有状态,然后枚举一遍这些合法的状态,把两遍的状态合到一起在相应的位置标记一下,最后在扫一遍所有状态累计答案,这样可以避免重复的方案
所以我们就在每个 s u m sum sum建一个vector,把相同 s u m sum sum的状态都放进去
因为 s u m ≤ 20 ∗ 1 0 8 sum \le20*10^8 sum20108,直接每个开个vector空间肯定会爆,所以我们就给依次出现的 s u m sum sum标个号,防止开不会用到的vector

s u m sum sum标号,就用一个map来存(这样每个开个map不会爆)
有几个很坑的点:map不同于数组,每次调用费时会多!!!所以如果要重复调用map里的同一个值,就先把它拿个变量存,然后用变量!!!求vector的size也一样!!!


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#define N 30
#define LL long long
using namespace std;

map<int, int> id;
vector<int> q[2000000];
int n, tot, a[N];
LL ans;
bool ok[2000000];

void dfs1 (int now, int sum, int qq)
{
    
    
	if (now > n / 2)
	{
    
    
		if (!qq) return;
		if (!sum) ok[qq] = 1;
		if (!id[sum]) id[sum] = ++tot;
		q[id[sum]].push_back (qq);
		return;
	}
	dfs1 (now + 1, sum, qq);
	dfs1 (now + 1, sum + a[now], qq | (1 << (now - 1)));
	dfs1 (now + 1, sum - a[now], qq | (1 << (now - 1)));
}
void dfs2 (int now, int sum, int qq)
{
    
    
//	printf ("%d\n", qq);
	if (now > n)
	{
    
    
		if (!qq) return;
		if (!sum) ok[qq] = 1;
		if (id[sum])
		{
    
    
			int len = q[id[sum]].size(), x = id[sum];
			for (int i = 0; i < len; i++)
			  ok[qq | q[x][i]] = 1;
		}
		return;
	}
	dfs2 (now + 1, sum, qq);
	dfs2 (now + 1, sum + a[now], qq | (1 << (now - 1)));
	dfs2 (now + 1, sum - a[now], qq | (1 << (now - 1)));
}

int main()
{
    
    
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) scanf ("%d", &a[i]);
	dfs1 (1, 0, 0);
	dfs2 (n / 2 + 1, 0, 0);
	for (int i = 1; i <= (1 << n); i++)
	  ans += ok[i];
	printf ("%lld", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43010386/article/details/121150962