无平方子集计数

题目

6364. 无平方子集计数


给你一个正整数数组 nums

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

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

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

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

示例 1:

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

示例 2:

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

提示:

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

题解

方法一:

思路

我们考虑每个数的质因数分解。
如果一个数有两个相同的质因数则说明这个数是非无平方因子数
无平方因子数的质因子每种只有一个,注意到每个数的大小不超过30,30内质数只有10个。

我们用10位二进制数表示一个数质因子出现情况。

$f_{i,j}$为前i个数中选取若干个使得乘积为j的选取种数。

$f_{0,0} = 1$

对于第i个数考虑选和不选。

不选,$f_{i,j}$由$f_{i-1,j}$转移。

选择,当$j \cap mask_i = \varnothing$时,$f_{i,j|mask_i}$由$f_{i-1,j}+1$贡献,$mask_i$是第i个数的质因数集合表示,若存在多个相同的质因数则设置为不存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
ll f[n+1][1<<10]; memset(f, 0, sizeof(f));
f[0][0] = 1;
for (int i=1; i<=n; i++) {
for (int j=0; j<1024; j++) {
f[i][j] = f[i-1][j];
}
for (int j=0; j<1024; j++) {
if (mask[i-1]>=0 && (j & mask[i-1]) == 0) {
f[i][j|mask[i-1]] += f[i-1][j];
f[i][j|mask[i-1]] %= MOD;
}
}
}

或者$f_{i,j}$由$f_{i-1, j-mask_i}+1$转移,$j-mask_i$集合$j$移除集合$mask_i$,也就是说要保证$mask_i \in j$
即$f_{i,j} = f_{i-1, j} + f_{i-1, j-mask_i}$,和01背包几乎一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ll f[n+1][1<<10]; memset(f, 0, sizeof(f));
f[0][0] = 1;
for (int i=1; i<=n; i++) {
// for (int j=0; j<1024; j++) {
// f[i][j] = f[i-1][j];
// }
for (int j=0; j<1024; j++) {
f[i][j] = f[i-1][j];
if (mask[i-1]>=0 && (j | mask[i-1]) == j) {
f[i][j] += f[i-1][j^mask[i-1]];
f[i][j] %= MOD;
}
}
}

由于我们用一个二进制数表示集合,当一个集合a是另一个集合b的子集时,所代表的二进制数同样a < b。基于这一点我们可以类似01背包用滚动数组以减小空间复杂度。

1
2
3
4
5
6
7
8
9
10
ll f[1<<10]; memset(f, 0, sizeof(f));
f[0] = 1;
for (int i=1; i<=n; i++) {
for (int j=1023; j>=0; j--) {
if (mask[i-1]>=0 && (j | mask[i-1]) == j) {
f[j] += f[j^mask[i-1]];
f[j] %= MOD;
}
}
}

最后$-1+\sum\limits_{j=0}^{1023}f_{n, j}$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
using ll = long long;
const ll MOD = 1e9+7;
int squareFreeSubsets(vector<int>& nums) {
vector<int> p = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int n = nums.size();
vector<int> mask(n);
for (int i=0; i<n; i++) {
int ok = 1;
for (int j=0; j<10; j++) {
if (nums[i] % (p[j] * p[j]) == 0) {
ok = 0;
break;
}
while (nums[i] % p[j] == 0) {
mask[i] |= 1<<j;
nums[i] /= p[j];
}
}
if (!ok) mask[i] = -1;
}
// for (int i:mask) cout << i << ' '; cout << endl;
ll f[n+1][1<<10]; memset(f, 0, sizeof(f));
f[0][0] = 1;
for (int i=1; i<=n; i++) {
for (int j=0; j<1024; j++) {
f[i][j] = f[i-1][j];
}
for (int j=0; j<1024; j++) {
if (mask[i-1]>=0 && (j & mask[i-1]) == 0) {
f[i][j|mask[i-1]] += f[i-1][j];
f[i][j|mask[i-1]] %= MOD;
}
}
}
int ans = 0;
for (int i=0; i<1024; i++) {
ans += f[n][i];
ans %= MOD;
}
return (ans-1+MOD)%MOD;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 滚动数组
class Solution {
public:
using ll = long long;
const ll MOD = 1e9+7;
int squareFreeSubsets(vector<int>& nums) {
vector<int> p = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int n = nums.size();
vector<int> mask(n);
for (int i=0; i<n; i++) {
int ok = 1;
for (int j=0; j<10; j++) {
if (nums[i] % (p[j] * p[j]) == 0) {
ok = 0;
break;
}
while (nums[i] % p[j] == 0) {
mask[i] |= 1<<j;
nums[i] /= p[j];
}
}
if (!ok) mask[i] = -1;
}
// for (int i:mask) cout << i << ' '; cout << endl;
ll f[1<<10]; memset(f, 0, sizeof(f));
f[0] = 1;
for (int i=1; i<=n; i++) {
for (int j=1023; j>=0; j--) {
if (mask[i-1]>=0 && (j | mask[i-1]) == j) {
f[j] += f[j^mask[i-1]];
f[j] %= MOD;
}
}
}
int ans = 0;
for (int i=0; i<1024; i++) {
ans += f[i];
ans %= MOD;
}
return (ans-1+MOD)%MOD;
}
};