题目
https://www.luogu.com.cn/problem/P3067
思路
考虑折半搜索,第一个dfs对[1,n/2]的数进行分组,+代表第一组,-代表第二组,并计算两组总和的情况方案数\(a_i\)。
第二个dfs对[n/2+1,n]的数统计数字和为sum的方案\(b_i\),同时与前n/2个数的方案进行匹配,寻找\(a_i\)中和为-sum的方案。两者和为1代表一种结果。
分组的去重方法:
考虑状态压缩,给每一个数分配一个二进制位(1代表选择,0代表不选),那么对应的每一种方案数获得一种二进制编号。
将\(a_i\)和\(b_i\)对应的二进制编号拼在一起,并判断重复即可。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<unordered_map>
const int maxm = (1 << 20) + 10;
const int maxn = 30 + 5;
std::unordered_map<long long, std::vector<int> > mp;
int num[maxn];
int n;
long long ans;
bool vis[maxm];
void dfs1(int x, int sum, int border, int id) {
if (x > border) {
mp[sum].push_back(id);
return;
}
dfs1(x + 1, sum + num[x], border, (id << 1) | 1);//左侧
dfs1(x + 1, sum - num[x], border, (id << 1) | 1);//右侧
dfs1(x + 1, sum, border, id << 1);//不选
}
void dfs2(int x, int sum, int border, int id) {
if (x > border) {
for (int i: mp[-sum]) {
if (vis[(i << 10) | id]) continue;//去重
vis[(i << 10) | id] = 1;
ans++;
}
return;
}
dfs2(x + 1, sum + num[x], border, (id << 1) | 1);//左侧
dfs2(x + 1, sum - num[x], border, (id << 1) | 1);//右侧
dfs2(x + 1, sum, border, id << 1);//不选
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
}
dfs1(1, 0, n / 2, 0);
dfs2(n / 2 + 1, 0, n, 0);
std::cout << ans - 1;
}
标签:折半,洛谷,P3067,int,sum,include,border,id
From: https://www.cnblogs.com/MrWangnacl/p/16776632.html