上海计算机学会 2023年11月月赛 乙组T4 平分子集(三)(状态压缩 折半枚举)

本文介绍了如何通过状态压缩和折半枚举策略解决给定集合中可平分子集的数量问题。作者提到暴力搜索方法效率低,通过二进制状态表示和预处理来减少重复计算,最终实现O(2^n)的时间复杂度。
摘要由CSDN通过智能技术生成

第四题:T4平分子集(三)

标签:状态压缩、折半枚举
题意:一个集合被称之为可平分的,如果它可以被分为两部分,且两部分的元素之和相等。空集也算可平分的。给定一个集合 a 1 , a 2 , a 3 , … , a n a_1,a_2,a_3,…,a_n a1,a2,a3,,an,请统计它有多少子集是可平分的。(本题中所指的集合允许元素相等)( 1 < = n < = 20 , 1 < = a i < = 1 0 7 1<=n<=20,1<=a_i<=10^7 1<=n<=201<=ai<=107
题解:看到题目 n n n这么小,很容易想到暴力深搜的写法,对于每个数选或不选,然后在选择了的数中再跑一遍深搜看看,能不能分成两部分,然后两部分分别的元素之和相等,试了一下发现这样整体的时间复杂度还是比较大的,并且重复的部分不好处理。

重复的部分,我们会去想 能不能通过状态压缩 把每个数选择的情况根据二进制的情况进行处理一下。针对降低时间复杂度这部分,如果折半枚举题目写的多的同学,应该熟悉这个套路。

每个数有三种情况选择:不选、放在 A A A 集合、放在 B B B 集合。
我们设前半部分放到 A A A 集合内的所有数之和为 a a a,放在 B B B集合内的所有数之和为 b b b
我们设前半部分放到 A A A 集合内的所有数之和为 c c c,放在 B B B集合内的所有数之和为 d d d
那么, a + c = b + d a+c = b+d a+c=b+d,移项得到 a − b = d − c a-b=d-c ab=dc。( a − b a-b ab其实就是 A A A集合所有数之和减去 B B B集合所有数之和, d − c d-c dc A A A集合所有数之和减去 B B B集合所有数之和的相反数)

我们先预处理出前半部分的 a − b a-b ab情况,去存储一下所有得到当前 a − b a-b ab的状态情况(即二进制状态压缩的值),然后后半部分的时候 得到 c − d c-d cd情况的时候,去之前存储的部分 找一下有多少 − ( c − d ) = a − b -(c-d)=a-b (cd)=ab的状态。

为了避免重复,我们把着前半部分的对应值的状态和后半部分对应值的状态进行一下按位或,进行结合,标记一下这 n n n个数在选择哪些状态的情况下能够达到题目中的要求。
代码

#include <bits/stdc++.h>
using namespace std;

map<int, int> m;
int n, id = 0, a[25], ans[2000005];
vector<int> e[2000005];

// p: 选到第p个数
// sum: A集合所有数之和减去B集合所有数之和
// x: 当前选了哪些数, 二进制 状态压缩
void dfs1(int p, int sum, int x) {
    if (p > n / 2) {
        if (!m[sum]) m[sum] = ++id;
        e[m[sum]].push_back(x);
        return ;
    }
    dfs1(p + 1, sum, x);
    dfs1(p + 1, sum + a[p], x | (1<<(p-1)));
    dfs1(p + 1, sum - a[p], x | (1<<(p-1)));
}

void dfs2(int p, int sum, int x) {
    if (p > n) {
        // 从前半部分的枚举情况下找-sum的形成状态
        int len = e[m[-sum]].size();
        for (int i = 0; i < len; i++) {
            // 维护实际的值的状态 避免重复
            ans[e[m[-sum]][i] | x] = 1;
        }
        return ;
    }
    dfs2(p + 1, sum, x);
    dfs2(p + 1, sum + a[p], x | (1<<(p-1)));
    dfs2(p + 1, sum - a[p], x | (1<<(p-1)));
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    dfs1(1, 0, 0);
    dfs2(n / 2 + 1, 0, 0);
    int res = 0;
    for (int i = 0; i < (1<<n); i++) {
        if (ans[i] == 1) res++;
    }
    cout << res << endl;
    return 0;
}
  • 37
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值