0
点赞
收藏
分享

微信扫一扫

【220222】1994.好子集的数目

上古神龙 2022-02-22 阅读 53

状态转移DP

DP:dp[i][state] 好子集的数目

  • i: [2, i]范围内的数,i在30以内
  • state:10个质数的选中情况

逻辑:

  • 每个好子集里,每个质数最多出现一次
  • DP基于【对于10个质数,各有 选 or 不选 的情况】
  • 对于每个数字,确定有唯一的state
  • PD基于【对于每个元素,有 选 or 不选 的情况】
  • 选择元素的条件:state不能与i-1时の,同时为1

编程技巧:

  • 遍历顺序:不按照数组顺序,按照自然顺序再剪枝。
    【数字大小范围限定在30以内】【求总数,与顺序无关】
    剪枝:freq数组记录某个数字的出现次数,不涉及的用0来continue
//官方题解
class Solution {
    static final int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
    static final int NUM_MAX = 30;
    static final int MOD = 1000000007;

    public int numberOfGoodSubsets(int[] nums) {
    	//遍历一遍计算数字的出现次数
        int[] freq = new int[NUM_MAX + 1];
        for (int num : nums) {
            ++freq[num];
        }

        int[] f = new int[1 << PRIMES.length];
        f[0] = 1;
        //讨论数字1
        for (int i = 0; i < freq[1]; ++i) {
            f[0] = f[0] * 2 % MOD;
        }
        
        //对于每个数字范围(从2开始)
        for (int i = 2; i <= NUM_MAX; ++i) {
            if (freq[i] == 0) {
                continue;
            }
            
            // 检查 i 的每个质因数是否均不超过 1 个
            int subset = 0, x = i;
            boolean check = true;
            //对于每个质数
            for (int j = 0; j < PRIMES.length; ++j) {
                int prime = PRIMES[j];
                //数字为质数的平方则滤去
                if (x % (prime * prime) == 0) {
                    check = false;
                    break;
                }
                //数字为质数,则得到其自身的使用情况二进制编码
                if (x % prime == 0) {
                    subset |= (1 << j);
                }
            }
            
            //数字为质数的平方则滤去
            if (!check) {
                continue;
            }

            // 动态规划
            for (int mask = (1 << PRIMES.length) - 1; mask > 0; --mask) {
            	//如果mask包含(i唯一对应的)subset
                if ((mask & subset) == subset) {
                //编程技巧:倒序遍历(大mask需要用到上一轮的小mask)
                    f[mask] = (int) ((f[mask] + ((long) f[mask ^ subset]) * freq[i]) % MOD);
                }
            }
        }	//对于每个数字范围

        int ans = 0;
        for (int mask = 1, maskMax = (1 << PRIMES.length); mask < maskMax; ++mask) {
            ans = (ans + f[mask]) % MOD;
        }
        
        return ans;
    }
}
举报

相关推荐

0 条评论