洛谷 P3067 [USACO12OPEN]Balanced Cow Subsets G 折半搜索

题目

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\)对应的二进制编号拼在一起,并判断重复即可。因为\(n \leq 20\),所以只需要开10位的空间留给\(b_i\)的编号。

时间复杂度\(O(n3^{n/2}+3^{n/2}log{3^{n/2}})\),不开O2要T2个点。

因为push_back()均摊复杂度是\(O(n)\),改成手写大概就行了(?)

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;
}
posted @ 2022-10-10 17:48  SxtoxA  阅读(55)  评论(0)    收藏  举报
12 13