利用位运算求集合的所有子集

一、分析

        我们知道,二进制的数由0和1组成。如十进制数0-7,它们二进制如下表所示。通过观察,我们发现这8个数的二进制刚好是0和1排列组合的8种情况。

十进制  二进制
0000
1001
2010
3011
4100
5101
6110
7111

        因此,在求集合的子集时,我们可以借助二进制数的进制的性质来决策是否取其中的某一个数来构成该子集。如100可以表示只取左边第1个数,101010可以表示只取左边第1、2、4个数。

        为了阐明该算法的实现,我们先假设一个集合A=(1,2,3,4),其长度为n=4;

        通过手算,我们可以得出A的所有子集为∅、(1)、(2)、(3)、(4)、(1,2)、(1,3)、(1,4),(2,3)、(2,4)、(3,4)、(1,2,3)、(1,2,4)、(1,3,4)、(2,3,4)、(1,2,3,4)。

       首先,我们要求出代表这些集合的二进制数的范围。如∅表示不取任何数,那么它对应的二进制数为0000,即十进制的0;(1,2,3,4)表示取所有数,那么它对应的二进制数为1111,即十进制的15。

        由二进制数的性质,我们可以知道:

        规定一个位数为n、非负的二进制数,如果其前n位均为0,那么这个数最小;相反地,如果其前n位均为1,那么这个数最大。如4位数的二进制数中,0000最小,1111最大。

        所以,我们可以得出代表这些集合的二进制数的取值范围为 02^{n-1}

        求出范围后,我们可以通过循环穷举出所有情况,代码如下:

    /**
     * 输出集合的所有子集
     * @param array 集合
     */
    public static void displaySubset(int[] array){
        /*
            记 up为2的”集合长度“次方
            up的取值为 0、1、2、3、4、5...2^array.length
         */
        int up = 1 << array.length;     //用位运算来完成2的幂运算

        //i递增循环,列举出所有情况
        for(int i = 0; i < up; i++){
            List<Integer> list = new ArrayList<>();
            //逐个取出每一位的值
            for(int j = 0; j < array.length; j++){
                if(((i>>j)&1) == 1){
                    //该位为1时,添加到列表
                    list.add(array[j]);
                }
            }
            //输出子集
            System.out.println("子集"+(i+1)+"-->"+list);
        }
    }

二、案例

2023年第十四届蓝桥杯JAVA B组题目 试题C【数组分割】

时间限制: 1.0s 内存限制: 512.0MB 本题总分:10 分

【问题描述】
小蓝有一个长度为 N 的数组 A = [A0, A1, . . . , AN−1]。
现在小蓝想要从 A 对 应的数组下标所构成的集合 I = {0, 1, 2, . . . , N − 1} 中找出一个子集 R1,
那么 R1在 I 中的补集为 R2。
记 S 1 = ∑r∈R1 Ar,
  S 2 = ∑r∈R2 Ar,
我们要求 S 1 和 S 2 均为 偶数,请问在这种情况下共有多少种不同的 R1。
当 R1 或 R2 为空集时我们将 S 1 或 S 2 视为 0。

【输入格式】
第一行一个整数 T,表示有 T 组数据。 接下来输入 T 组数据,每组数据包含两行:第一行一个整数 N,表示数组
A 的长度;第二行输入 N 个整数从左至右依次为 A0, A1, . . . , AN−1,相邻元素之 间用空格分隔。

【输出格式】
对于每组数据,输出一行,包含一个整数表示答案,答案可能会很大,你 需要将答案对 1000000007 进行取模后输出。

【输入样例】
 2
 2
 6 6
 2
 1 6

【输出样例】
 4
 0

【样例说明】
对于第一组数据,答案为 4。(注意:大括号内的数字表示元素在数组中的 下标。)
R1 = {0}, R2 = {1};此时 S 1 = A0 = 6 为偶数, S 2 = A1 = 6 为偶数。
R1 = {1}, R2 = {0};此时 S 1 = A1 = 6 为偶数, S 2 = A0 = 6 为偶数。
R1 = {0, 1}, R2 = {};此时 S 1 = A0 + A1 = 12 为偶数, S 2 = 0 为偶数。
R1 = {}, R2 = {0, 1};此时 S 1 = 0 为偶数, S 2 = A0 + A1 = 12 为偶数。 对于第二组数据,无论怎么选择,都不满足条件,所以答案为 0。

【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ N ≤ 10。 对于 40% 的评测用例,1 ≤ N ≤ 10 2。 对于 100% 的评测用例,1 ≤ T ≤ 10, 1 ≤ N ≤ 10 3 , 0 ≤ Ai ≤ 10 9。

采用本问思想进行解答,Java代码如下,时间复杂度为O(n^{2})

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    

    public static long compute(int[] a){

        long ans = 0;
        int up = 1 << a.length;
        for(int i = 0; i < up; i++){
            int s1 = 0;
            int s2 = 0;

            for(int j = 0; j < a.length; j++){
                if(((i>>j)&1) == 1){
                    s1 += a[j];
                }else{
                    s2 += a[j];
                }
            }

            //同时为偶数时满足条件
            if((s1&1) == 0 && (s2&1) == 0){
                ans = (ans+1)%1000000007;
            }
        }
        return ans;
    }



    public static void main(String[] args) {


        displaySubset(new int[]{1,2,3,4});

        Scanner scanner = new Scanner(System.in);

        int t = scanner.nextInt();
        long[] res = new long[t];

        for(int i = 0; i < t; i++){
            int n = scanner.nextInt();
            int[] a = new int[n];
            for(int j = 0; j < n; j++){
                a[j] = scanner.nextInt();
            }
            res[i] = compute(a);
        }

        for (long re : res) {
            System.out.println(re);
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值