五月集训(第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因子出现的可能性,乘积即为答案 */
}
};
东方欲晓,莫道君行早。

浙公网安备 33010602011771号