Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[pull] main from itcharge:main #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
pull merged 6 commits into AlgorithmAndLeetCode:main from itcharge:main
May 16, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Update 1994. 好子集的数目.md
  • Loading branch information
itcharge committed May 16, 2023
commit 6a8de97488a82e87b1c62b59a50c47de7be6151d
84 changes: 58 additions & 26 deletions Solutions/1994. 好子集的数目.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
输入:nums = [1,2,3,4]
输出:6
解释:好子集为:
- [1,2]:乘积为 2,可以表示为质数 2 的乘积。
- [1,2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。
- [1,3]:乘积为 3,可以表示为质数 3 的乘积。
- [2]:乘积为 2,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。
- [3]:乘积为 3,可以表示为质数 3 的乘积。
- [1,2]:乘积为 2,可以表示为质数 2 的乘积。
- [1,2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。
- [1,3]:乘积为 3,可以表示为质数 3 的乘积。
- [2]:乘积为 2,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6,可以表示为互不相同的质数 2 和 3 的乘积。
- [3]:乘积为 3,可以表示为质数 3 的乘积。
```

- 示例 2:
Expand All @@ -43,18 +43,49 @@
输入:nums = [4,2,3,15]
输出:5
解释:好子集为:
- [2]:乘积为 2,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6,可以表示为互不相同质数 2 和 3 的乘积。
- [2,15]:乘积为 30,可以表示为互不相同质数 2,3 和 5 的乘积。
- [3]:乘积为 3,可以表示为质数 3 的乘积。
- [15]:乘积为 15,可以表示为互不相同质数 3 和 5 的乘积。
- [2]:乘积为 2,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6,可以表示为互不相同质数 2 和 3 的乘积。
- [2,15]:乘积为 30,可以表示为互不相同质数 2,3 和 5 的乘积。
- [3]:乘积为 3,可以表示为质数 3 的乘积。
- [15]:乘积为 15,可以表示为互不相同质数 3 和 5 的乘积。
```

## 解题思路

### 思路 1:状态压缩 DP

根据题意可以看出:

1. 虽然 $nums$ 的长度是 $[1, 10^5],ドル但是其值域范围只有 $[1, 30],ドル则我们可以将 $[1, 30]$ 的数分为 3ドル$ 类:
1. 质数:$[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]$(共 10ドル$ 个数)。由于好子集的乘积拆解后的质因子只能包含这 10ドル$ 个,我们可以使用一个数组 $primes$ 记录下这 10ドル$ 个质数,将好子集的乘积拆解为质因子后,每个 $primes[i]$ 最多出现一次。
2. 非质数:$[4, 6, 8, 9, 10, 12, 14, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30]$。非质数肯定不会出现在好子集的乘积拆解后的质因子中。
3. 特殊的数:$[1]$。对于一个好子集而言,无论向中间添加多少个 1ドル,ドル得到的新子集仍是好子集。
2. 分类完成后,由于 $[1, 30]$ 中只有 10ドル$ 个质数,因此我们可以使用一个长度为 10ドル$ 的二进制数 $state$ 来表示 $primes$ 中质因数的选择情况。其中,如果 $state$ 第 $i$ 位为 1ドル,ドル则说明第 $i$ 个质因数 $primes[i]$ 被使用过;如果 $state$ 第 $i$ 位为 0ドル,ドル则说明第 $i$ 个质因数 $primes[i]$ 没有被使用过。
3. 题目规定值相同,但是下标不同的子集视为不同子集,那么我们可先统计出 $nums$ 中每个数 $nums[i]$ 的出现次数,将其存入 $cnts$ 数组中,其中 $cnts[num]$ 表示 $num$ 出现的次数。这样在统计方案时,直接计算出 $num$ 的方案数,再乘以 $cnts[num]$ 即可。

接下来,我们就可以使用「动态规划」的方式来解决这道题目了。

###### 1. 划分阶段

按照质因数的选择情况进行阶段划分。

###### 2. 定义状态

定义状态 $dp[state]$ 表示为:当质因数选择的情况为 $state$ 时,好子集的数目。

###### 3. 状态转移方程

对于 $nums$ 中的每个数 $num,ドル其对应出现次数为 $cnt$。我们可以通过试除法,将 $num$ 分解为不同的质因数,并使用「状态压缩」的方式,用一个二进制数 $cur\underline{}state$ 来表示当前数 $num$ 中使用了哪些质因数。然后枚举所有状态,找到与 $cur\underline{}state$ 不冲突的状态 $state$(也就是除了 $cur\underline{}state$ 中选择的质因数外,选择的其他质因数情况,比如 $cur\underline{}state$ 选择了 2ドル$ 和 5ドル,ドル则枚举不选择 2ドル$ 和 5ドル$ 的状态)。

此时,状态转移方程为:$dp[state | cur\underline{}state] = \sum (dp[state] * cnt) \mod MOD ,\quad state \& cur\underline{}state == 0$

###### 4. 初始条件

- 当 $state == 0,ドル所选质因数为空时,空集为好子集,则 $dp[0] = 1$。同时,对于一个好子集而言,无论向中间添加多少个 1ドル,ドル得到的新子集仍是好子集,所以对于空集来说,可以对应出 2ドル^{cnts[1]}$ 个方案,则最终 $dp[0] = 2^{cnts[1]}$。

###### 5. 最终结果

根据我们之前定义的状态,$dp[state]$ 表示为:当质因数的选择的情况为 $state$ 时,好子集的数目。 所以最终结果为所有状态下的好子集数目累积和。所以我们可以枚举所有状态,并记录下所有好子集的数目和,就是最终结果。

### 思路 1:代码

Expand All @@ -66,32 +97,33 @@ class Solution:

cnts = Counter(nums)
dp = [0 for _ in range(1 << len(primes))]
dp[0] = pow(2, cnts[1], MOD)

for num, cnt in cnts.items(): # 遍历 nums 中所有数及其频数
if num == 1: # 跳过 1
dp[0] = pow(2, cnts[1], MOD) # 计算 1

# num 分解质因数
for num, cnt in cnts.items(): # 遍历 nums 中所有数及其频数
if num == 1: # 跳过 1
continue
flag = True # 判断当前子集是否满足互不相同的质数相乘
cur_num = num

flag = True # 检查 num 的质因数是否都不超过 1
cur_num = num
cur_state = 0
for i, prime in enumerate(primes):
for i, prime in enumerate(primes): # 对 num 进行试除
cur_cnt = 0
while cur_num % prime == 0:
cur_cnt += 1
cur_state = cur_state | 1 << i
cur_state |= 1 << i
cur_num //= prime
if cur_cnt > 1:
if cur_cnt > 1: # 当前质因数超过 1,则 num 不能添加到子集中,跳过
flag = False
break
if not flag:
continue

for state in range(1 << len(primes)):
#
if state & cur_state == 0:
dp[state | cur_state] = (dp[state | cur_state] + dp[state] * cnts[num]) % MOD
if state & cur_state == 0: # 只有当前选择状态与前一状态不冲突时,才能进行动态转移
dp[state | cur_state] = (dp[state | cur_state] + dp[state] * cnt) % MOD

ans = 0
ans = 0 # 统计所有非空集合的方案数
for i in range(1, 1 << len(primes)):
ans = (ans + dp[i]) % MOD

Expand All @@ -100,5 +132,5 @@ class Solution:

### 思路 1:复杂度分析

- **时间复杂度**:
- **空间复杂度**:
- **时间复杂度**:$O(n + m \times 2^p),ドル其中 $n$ 为数组 $nums$ 的元素个数,$m$ 为 $nums$ 的最大值,$p$ 为 $[1, 30]$ 中的质数个数。
- **空间复杂度**:$O(2^p)$。

AltStyle によって変換されたページ (->オリジナル) /