题目描述
小E想知道一个给定集合a中,有多少个子集满足以下条件:
- 子集内的所有元素数量大于 1。
- 子集内的所有元素两两之间互为倍数关系。
由于结果可能非常大,输出的结果需要对 取模。
题目分析
无重复元素集合
这道题涉及数论和动态规划的结合,需要关注倍数关系的传递性,并高效地计算所有满足条件的子集。为了更容易地统计数目,我们将元素数量为1的子集也统计在内,最终只需将元素数量为1的个子集减去即可。
设子集的最大元素为k的,满足子集内两两互为倍数的子集个数为dp[k]。
一个子集中的所有元素两两互为倍数关系,意味着任何子集中的最大元素必须是其他元素的倍数,根据倍数关系可以将问题转化为分组问题,基于某个元素生成符合条件的子集。
具体来说,假设子集的最大元素为k,第二大的元素为d,这样的子集个数恰好与最大元素为d的子集个数相同,即dp[d]个,由此可以得到状态转移方程:
然而如果我们直接对每个 k 遍历它的所有因子 d,并对每个因子 d 累加贡献。由于找到一个数的所有因数的复杂度为,整个过程的时间复杂度为。
为此,我们改为对于每个d,遍历它的所有倍数k,为其增加贡献的个数。由于找到在中的所有倍数的复杂度为,这样,整个过程的复杂度就降低到了
有重复元素集合
然而,题目没有提及的是,本题的集合a是允许元素重复的,为此我们需要在上述思路的基础上进行修改。
具体来说,我们需要引入一个计数数组 cnt[x],表示集合中每个元素 x 的出现次数,从而对重复元素的子集贡献进行正确的统计。
对于集合中的任意一个元素 x,如果它出现了 cnt[x] 次,那么由 x 构成的所有非空子集的个数为。
这样,我们就将状态转移方程更新为:。在实际计算时,我们对于遍历d的倍数k,并为dp[k]增加,dp[d]*f[k]的贡献。
为了高效计算,我们还引入快速幂方法。快速幂是一种计算大幂次模运算的高效算法,其核心思想是通过二分法将指数分解为若干幂次的乘积,从而将计算复杂度从 降低到 。
代码实现
mod=10**9+7
def quick_pow(a,n):
res=1
while n>0:
if n&1:
res=(res*a)%mod
a=(a*a)%mod
n>>=1
return res
def solution(n: int, a: list) -> int:
max_num=max(a)
cnt={}
for item in a:
cnt[item]=cnt.get(item,0)+1
f={}
for k in cnt:
f[k]=(quick_pow(2,cnt[k])+mod-1)%mod
ans={k:f[k] for k in cnt}
for k in sorted(cnt):
j=2*k
while j<=max_num:
if j in cnt:
ans[j]=(ans[j]+ans[k]*f[j])%mod
j+=k
ans_sum=0
for k in ans:
ans_sum+=ans[k]
return ans_sum-n