五月集训(第31天)—状态压缩

状态压缩

1. 1994. 好子集的数目

嘤嘤嘤,好难
    思路:
        好不容易才看懂,具体思路参考了英雄哥的思路,如下
在这里插入图片描述

在这里插入图片描述
        代码实现的细节在题目中体现


class Solution {
    #define ll long long
    #define mod 1000000007
    int prime[10] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};   /* 0~30之间的素数 */
    int bad[11] = {4, 8, 9, 12, 16, 18, 20, 24, 25, 27, 28};  /* 0~30之间必然不能选的数(不能用互不相同质因数表示的数) */
    int code[32];
    int cnt[32];

    void init() {
        int i, j;
        memset(code, 0, sizeof(code));
        memset(cnt, 0, sizeof(cnt));
        for (i = 0; i < 10; ++i) code[ prime[i] ] = (1 << i);
        for (i = 0; i < 11; ++i) code[ bad[i] ] = -1;
        for (i = 2; i <= 30; ++i) {
            if (code[i] == 0) { /* 处理非prime和bad的数字,获取其质因数表示方法 */
                for (j = 0; j < 10; ++j) {
                    if (i % prime[j] == 0) code[i] |= code[ prime[j] ];
                }
            }
        }
        code[31] = 0;
        cnt[31] = 1;
    }

    // 快速幂求 base^power
    ll quick_pow(ll base,ll power){//power指数 base底数 
        ll result = 1;
        base %= mod;
        while(power){
            if(power&1){//若为odd 
                result=((result%mod)*(base%mod))%mod;//odd 保留一次 
            }	
            power>>=1;
            base=(base%mod*base%mod)%mod;//%从右向左的双目运算符 所以x*y%mod==X*(y%mod) 
        }
        return result;//最终要全部转化为一次,所以最后必然经过odd判定行,即直接返回result即可 
    } 

    ll dfs(int start, int mask) {   // 返回start选与不选对应的方案数
        int i;
        if (start == 32) return 1;
        ll ans = 0;
        for (i = start; i <= 31; ++i) { /* i <= 31 防止缺失不选30的情况,但是多了选中31的情况(cnt[31] = 1,在返回的结果中-1即可) */
            if (cnt[i] == 0) continue;  // 该数字没出现过
            if (code[i] == -1) continue;    // 该数字不满足条件,不能选
            if (code[i] & mask) continue;   // 已经出现过了
            ans += dfs(i + 1, mask | code[i]) * cnt[i] % mod;   /* 标记当前数字出现过,mask加入其编码code[i] */
            ans %= mod;
        }
        return ans;
    }

public:
    int numberOfGoodSubsets(vector<int>& nums) {
        init();
        int nums_size = nums.size(), i;
        for (i = 0; i < nums_size; ++i) {
            ++cnt[ nums[i] ];   /* 记录0~30每个数字出现的次数 */
        }
        return quick_pow(2, cnt[1]) * (dfs(2, 0) - 1) % mod;    /* cnt[1]个1,选择方式有2^cnt[1]种;再找出2~30因子出现的可能性,乘积即为答案 */
    }
};
posted @ 2022-06-04 17:34  番茄元  阅读(25)  评论(0)    收藏  举报