题解:SP11469 SUBSET - Balanced Cow Subsets

双倍经验:P3067

题意简述

给定长度为 nn 的一个序列 aa,对于每一个数 aia_i,可以把它加入 AABB 两个集合之一或者不加入任何一个集合,要求所有数处理完后,AABB 两个集合中数的和相等,求方案数。

n20n\le20ai108a_i\le 10^8

思路

对于一个数 aia_i,在最后有三种状态:

  • 它在 AA 集合。
  • 它在 BB 集合。
  • 它不在任何一个集合中。

所以如果我们暴搜复杂度是 O(3n)\operatorname{O}(3^n) 的。而 320=34867844013^{20}=3486784401,因此暴搜自然过不去。

我们只好使用折半搜索将时间复杂度降到 O(3n2)\operatorname{O}(3^{\frac{n}{2}})

如何折半搜索?

首先我们将序列分成 [1,n2][1,\dfrac{n}{2}][n2+1,n][\dfrac{n}{2}+1,n] 两部分,分别搜索。

我们首先证明一个等式:设 [1,n2][1,\dfrac{n}{2}] 区间内 AA 集合的数的和为 p1p_1BB 集合的和为 q1q_1。同理 [n2+1,n][\dfrac{n}{2}+1,n] 区间内 AA 集合的数的和为 p2p_2BB 集合的和为 q2q_2

q1p1=p2q2q_1-p_1=p_2-q_2

证明:由题意可得 p1+p2=q1+q2p_1+p_2=q_1+q_2,移项可得上面的式子。

对于 [1,n2][1,\dfrac{n}{2}] 的部分,我们先暴力搜索,然后当搜索完成后,记录 q1p1q_1-p_1 的值。

对于 [n2+1,n][\dfrac{n}{2}+1,n] 的部分,我们也是先暴力搜索,同时记录 p2q2p_2-q_2。在搜索完成后进行匹配,得到答案。

写出代码:

map<long long,int>mp; 
void dfs1(int now,long long sum){
	if(now==n/2+1){
		mp[sum]++;
		return; 
	}
	dfs1(now+1,sum);
	dfs1(now+1,sum+a[now]);
	dfs1(now+1,sum-a[now]);
}
long long ans;
void dfs2(int now,long long sum){
	if(now==n+1){
		if(sum!=0)ans+=mp[sum];//0不能统计
		return;
	}
	dfs2(now+1,sum);
	dfs2(now+1,sum-a[now]);
	dfs2(now+1,sum+a[now]);
}

但是这个代码是错误的。

对于序列 a={1,2,1,2}a=\{1,2,1,2\} 而言:

我们将 a1a_1a4a_4 放入 AA 集合,其余的放入 BB 集合和 a2a_2a3a_3 放入 AA 集合中是一种方案,因为我们都是选择了相同的 44 个数。

显然这个代码会重复统计。

所以我们要用一个二进制数,存储每个点是否被选。11 代表选了。然后按照二进制位把它压成一个十进制数。如果统计答案是这个状态已经算过了,就不算了。

AC Code

#include<bits/stdc++.h>
using namespace std;
int n,a[25];
map<int,vector<int> >mp; 
void dfs1(int now,long long sum,int S){
	if(now==n/2+1){
		mp[sum].push_back(S);
		return; 
	}
	dfs1(now+1,sum,S);
	dfs1(now+1,sum+a[now],S|(1<<(now-1)));
	dfs1(now+1,sum-a[now],S|(1<<(now-1)));
}
long long ans;
bool b[1048577];
void dfs2(int now,long long sum,int S){
	if(now==n+1){
		if(mp.count(sum)){
			for(int S2:mp[sum]){
				if(b[S+S2]==0)ans++;
				b[S+S2]=1;
//				printf("%d\n",S+S2);
			}
		}
		return;
	}
	dfs2(now+1,sum,S);
	dfs2(now+1,sum-a[now],S|(1<<(now-1)));
	dfs2(now+1,sum+a[now],S|(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);
	printf("%d",(ans-1));
	
	return 0;
}
posted @   Weslie_qwq  阅读(3)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示