小E的倍数关系子集问题题解 | 豆包MarsCode AI刷题

69 阅读3分钟

题目描述

小E想知道一个给定集合a中,有多少个子集满足以下条件:

  • 子集内的所有元素数量大于 1。
  • 子集内的所有元素两两之间互为倍数关系。

由于结果可能非常大,输出的结果需要对 109+710^9+7 取模。

题目分析

无重复元素集合

这道题涉及数论和动态规划的结合,需要关注倍数关系的传递性,并高效地计算所有满足条件的子集。为了更容易地统计数目,我们将元素数量为1的子集也统计在内,最终只需将元素数量为1的nn个子集减去即可。

设子集的最大元素为k的,满足子集内两两互为倍数的子集个数为dp[k]

一个子集中的所有元素两两互为倍数关系,意味着任何子集中的最大元素必须是其他元素的倍数,根据倍数关系可以将问题转化为分组问题,基于某个元素生成符合条件的子集。

具体来说,假设子集的最大元素为k,第二大的元素为d,这样的子集个数恰好与最大元素为d的子集个数相同,即dp[d]个,由此可以得到状态转移方程:

dp[k]=1+dkdp[d]dp[k]=1+\sum_{d|k}dp[d]

然而如果我们直接对每个 k 遍历它的所有因子 d,并对每个因子 d 累加贡献。由于找到一个数kk的所有因数的复杂度为O(k)O(\sqrt{k}),整个过程的时间复杂度为O(max(a)32)O(\max(a)^{\frac{3}{2}})

为此,我们改为对于每个d,遍历它的所有倍数k,为其增加贡献dp[d]dp[d]的个数。由于找到ddmax(a)\max(a)中的所有倍数的复杂度为O(max(a)/d)O(\max(a)/d),这样,整个过程的复杂度就降低到了O(d=1max(a)max(a)d)=O(max(a)logmax(a))O(\sum_{d=1}^{\max(a)}\frac{\max(a)}{d})=O(\max(a)\log \max(a))

有重复元素集合

然而,题目没有提及的是,本题的集合a是允许元素重复的,为此我们需要在上述思路的基础上进行修改。

具体来说,我们需要引入一个计数数组 cnt[x],表示集合中每个元素 x 的出现次数,从而对重复元素的子集贡献进行正确的统计。

对于集合中的任意一个元素 x,如果它出现了 cnt[x] 次,那么由 x 构成的所有非空子集的个数为f[x]=2cnt[x]1f[x]=2^{\text{cnt}[x]} - 1

这样,我们就将状态转移方程更新为:dp[k]=f[k](1+dkdp[d])dp[k]=f[k](1+\sum_{d|k}dp[d])。在实际计算时,我们对于遍历d的倍数k,并为dp[k]增加,dp[d]*f[k]的贡献。

为了高效计算,我们还引入快速幂方法。快速幂是一种计算大幂次模运算的高效算法,其核心思想是通过二分法将指数分解为若干幂次的乘积,从而将计算复杂度从 O(n)O(n) 降低到 O(logn)O(\log n)

代码实现

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