2023-2-21 刷题情况

无平方子集计数

题目描述

给你一个正整数数组 nums 。

如果数组 nums 的子集中的元素乘积是一个 无平方因子数 ,则认为该子集是一个 无平方 子集。

无平方因子数 是无法被除 1 之外任何平方数整除的数字。

返回数组 nums 中 无平方 且 非空 的子集数目。因为答案可能很大,返回对 109 + 7 取余的结果。

nums 的 非空子集 是可以由删除 nums 中一些元素(可以不删除,但不能全部删除)得到的一个数组。如果构成两个子集时选择删除的下标不同,则认为这两个子集不同。

样例

样例输入

nums = [3,4,4,5]
nums = [1]

样例输出

3
解释:示例中有 3 个无平方子集:
由第 0 个元素 [3] 组成的子集。其元素的乘积是 3 ,这是一个无平方因子数。
由第 3 个元素 [5] 组成的子集。其元素的乘积是 5 ,这是一个无平方因子数。
由第 0 个和第 3 个元素 [3,5] 组成的子集。其元素的乘积是 15 ,这是一个无平方因子数。
可以证明给定数组中不存在超过 3 个无平方子集。

1
解释:示例中有 1 个无平方子集:
由第 0 个元素 [1] 组成的子集。其元素的乘积是 1 ,这是一个无平方因子数。
可以证明给定数组中不存在超过 1 个无平方子集。

提示

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 30

思路

题目的需求还是很清晰,需要求出所有区间内所有质因子不相同的区间数量,但题中有要求要对1e9 + 7求余,那么每次都遍历当前区间的质因子个数,就不现实。需要很清晰的表达出当前数或当前区间的所有质因子,周赛的时候我是没写出来,对于我来说还是难,是需要特地学的程度。

状态压缩dp,很难,但要是能自己写出来,感觉就能非常厉害。
题目的本质是有多少个质因子不相同的和。需要很方便的表示所有数的质因子,可以使用二进制来例举(巨厉害的想法)。

代码实现

01背包类解法

class Solution {
    public int squareFreeSubsets(int[] nums) {
        int[] assist = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
        int n = nums.length;
        int[] arr = new int[31];
        for(int i = 2; i < 31; i++){
            for(int j = 0; j < assist.length; j++){
                if(i % assist[j] == 0){
                    if(i % (assist[j] * assist[j]) == 0){
                        arr[i] = -1;
                        break;
                    }
                    arr[i] |= 1 << j;
                }
            }
        }
		// 以上都是把数据进行预处理
		// 转化为相对应的二进制形式

        int MOD = (int)1e9 + 7;
        int m = 1 << assist.length;
        int[] dp = new int[m];
        dp[0] = 1;
        for(int num : nums){
            int mask = arr[num];
            if(mask >= 0){
                for(int j = m-1; j >= mask; j--){
                    if((mask | j) == j)
                        dp[j] = (dp[j] + dp[j ^ mask]) % MOD;
                }
            }
        }
        var ans = 0L;
        for(int d : dp) ans += d;
		// 减1是需要减去dp的边界条件,如果dp[0]不初始化为1,那么dp数组全为0.
        return (int)((ans - 1) % MOD);
    }
}

状压DP(暴力版)

class Solution {
    static final int[] assist = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
    static final int MOD = (int)1e9 + 7, max = 30, n = assist.length, m = 1 << n;
    static final int[] arr = new int[max+1];
    static{
        for(int i = 2; i <= max; i++){
            for(int j = 0; j < n; j++){
                if(i % (assist[j]) == 0){
                    if(i % (assist[j] * assist[j]) == 0){
                        arr[i] = -1;
                        break;
                    }
                    arr[i] |= 1 << j;
                }
            }
        }
    } 
	// 以上都是数组的预处理

    public int squareFreeSubsets(int[] nums) {
        int n = nums.length;
        var cnt = new int[max + 1];
        int pow2 = 1;
        for(int x : nums){
            if(x == 1) pow2 = pow2 * 2 % MOD;
            else cnt[x]++;
        }
        var dp = new long[m];
        dp[0] = 1;
        for(int i = 2; i < 31; i++){
            int mask = arr[i], c = cnt[i];
            if(mask >= 0 && c > 0){
				// 直接枚举所有大于mask的情况,但只有mask & k == 0的情况才会进入状态转移。
				// 时间上还能进行优化。
                for(int k = m - 1; k >= 0; k--){
                    if((mask & k) == 0){
                        dp[mask | k] = (dp[mask | k] + dp[k] * c) % MOD;
                    }
                }
            }
        }
        var ans = 0L;
        for(var c : dp) ans += c;
        return (int)((ans % MOD * pow2 - 1) % MOD);
    }
}

状压DP(最优版)

class Solution {
    static final int[] assist = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
    static final int MOD = (int)1e9 + 7, max = 30, n = assist.length, m = 1 << n;
    static final int[] arr = new int[max+1];
    static{
        for(int i = 2; i <= max; i++){
            for(int j = 0; j < n; j++){
                if(i % (assist[j]) == 0){
                    if(i % (assist[j] * assist[j]) == 0){
                        arr[i] = -1;
                        break;
                    }
                    arr[i] |= 1 << j;
                }
            }
        }
    } 


    public int squareFreeSubsets(int[] nums) {
        int n = nums.length;
        var cnt = new int[max + 1];
        int pow2 = 1;
        for(int x : nums){
            if(x == 1) pow2 = pow2 * 2 % MOD;
            else cnt[x]++;
        }
        var dp = new long[m];
        dp[0] = 1;
        for(int i = 2; i < 31; i++){
            int mask = arr[i], c = cnt[i];
            if(mask >= 0 && c > 0){
                int other = (m-1) ^ mask, j = other;
                while(true){
                    dp[j | mask] = (dp[j | mask] + dp[j] * c) % MOD;
					// (j -1) & other = j -= j&(-j)结果差不多,都是减去最后一位1的方式。
					// 因为还需要枚举全0的情况,所以没用j > 0的情况控制循环
					// 因为-1的二进制形式为全1,所以最后j = 0时,还会进入一次循环,并且与and -1后等于自己
					// 所以可以使用 j == other 最为跳出循环的条件。
					// 当然使用do while(j != other) 控制循环也可。
                    j = (j - 1) & other;
                    if(j == other) break;
                }
            }
        }
        var ans = 0L;
        for(var c : dp) ans += c;
        return (int)((ans % MOD * pow2 - 1) % MOD);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值