题解:SP11469 SUBSET - Balanced Cow Subsets
双倍经验:P3067。
题意简述
给定长度为 的一个序列 ,对于每一个数 ,可以把它加入 和 两个集合之一或者不加入任何一个集合,要求所有数处理完后, 与 两个集合中数的和相等,求方案数。
且 。
思路
对于一个数 ,在最后有三种状态:
- 它在 集合。
- 它在 集合。
- 它不在任何一个集合中。
所以如果我们暴搜复杂度是 的。而 ,因此暴搜自然过不去。
我们只好使用折半搜索将时间复杂度降到 。
如何折半搜索?
首先我们将序列分成 和 两部分,分别搜索。
我们首先证明一个等式:设 区间内 集合的数的和为 , 集合的和为 。同理 区间内 集合的数的和为 , 集合的和为 。
则 。
证明:由题意可得 ,移项可得上面的式子。
对于 的部分,我们先暴力搜索,然后当搜索完成后,记录 的值。
对于 的部分,我们也是先暴力搜索,同时记录 。在搜索完成后进行匹配,得到答案。
写出代码:
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]);
}
但是这个代码是错误的。
对于序列 而言:
我们将 和 放入 集合,其余的放入 集合和 和 放入 集合中是一种方案,因为我们都是选择了相同的 个数。
显然这个代码会重复统计。
所以我们要用一个二进制数,存储每个点是否被选。 代表选了。然后按照二进制位把它压成一个十进制数。如果统计答案是这个状态已经算过了,就不算了。
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;
}

浙公网安备 33010602011771号