力扣 1994. 好子集的数目 数学 状压dp

48 篇文章 1 订阅
6 篇文章 0 订阅

https://leetcode-cn.com/problems/the-number-of-good-subsets/
在这里插入图片描述
思路一:纯数学暴力解法。首先我们知道一个合数一定可以表示为若干个素数的乘积,因为要求素数不能相同,因此子集中的合数一定不能表示成两个相同素数的乘积。注意到元素的取值范围为 [ 1 , 30 ] [1,30] [1,30],据此我们可以排除一些合数。然后把相同的数合并到一起(记录出现的次数),那么就只剩下大概20个元素了。此时可以暴力枚举选择的情况,然后对选择的数做因数分解统计每个质因子出现的次数,如果不合法就不计数。注意如果合法的话,由于每个元素可能出现了多次,任意选择某一个都是一种新的情况,因此计数时要使用乘法原理。最后特殊考虑数字1,因为任意一种合法情况加上任意个1都是合法的,所以我们可以把1放到最后考虑,不妨设1出现的次数为 c n t cnt cnt,其他情况的计数总和为 s u m sum sum,最终答案就等于 s u m ∗ 2 c n t sum*2^{cnt} sum2cnt。总复杂度为 O ( n ∗ 2 n ) O(n*2^{n}) O(n2n),其中 n n n为合法的数字数量。

class Solution {
public:
    const int mod=1e9+7;
    using ll=long long;
    int qpow(ll a,ll b)
    {
        ll ans=1;
        while(b)
        {
            if(b&1)
                ans=(ans*a)%mod;
            b>>=1;
            a=(a*a)%mod;
        }
        return ans;
    }
    
    int numberOfGoodSubsets(vector<int>& vec) {
        set<int> target={2,3,5,6,7,10,11,13,14,15,17,19,21,22,23,26,29,30};
        vector<int> cf[31];
        cf[2]={2};
        cf[3]={3};
        cf[5]={5};
        cf[6]={2,3};
        cf[7]={7};
        cf[10]={2,5};
        cf[11]={11};
        cf[13]={13};
        cf[14]={2,7};
        cf[15]={3,5};
        cf[17]={17};
        cf[19]={19};
        cf[21]={3,7};
        cf[22]={2,11};
        cf[23]={23};
        cf[26]={2,13};
        cf[29]={29};
        cf[30]={2,3,5};
        vector<int> nums;
        int cnt[31]={0};
        for(int v:vec)
        {
            if(target.count(v))
            {
                if(cnt[v]==0)
                    nums.push_back(v);
                ++cnt[v];
            }
            else if(v==1)
                ++cnt[1];
        }
        int n=nums.size(),times=1<<n;
        ll ans=0;
        for(int i=1;i<times;i++)
        {
            bool ok=true;
            bool vis[31]={0};
            ll num=1;
            for(int j=0;j<n;j++)
            {
                if(i&(1<<j))
                {
                    for(int factor:cf[nums[j]])
                    {
                        if(vis[factor])
                        {
                            ok=false;
                            break;
                        }
                        vis[factor]=1;
                    }
                    num=num*cnt[nums[j]]%mod;
                }
                if(!ok)
                    break;
            }
            if(ok)
                ans=(ans+num)%mod;
        }
        ans=ans*qpow(2,cnt[1])%mod;
        return ans;
    }
};

思路二:其实上面的思路还有很大的优化空间,我们可以根据合数和质因子的关系进行剪枝,比如当选择了6就一定不能选择2、3等等。更近一步,因为合数可以由质数相乘得到,所以本质上还是考虑质数的选择情况。考虑到 [ 0 , 30 ] [0,30] [0,30]一共有10个质数,可以用二进制状态表示选择的质数的情况,用 d p [ i ] [ s t a t e ] dp[i][state] dp[i][state]表示在前 i i i个数中选择的质数的状态为 s t a t e state state时所对应的情况数,由思路一知 i i i最大可取 18 18 18,那么复杂度就优化到了 O ( 18 ∗ 2 10 ) O(18*2^{10}) O(18210)。转移方程有为:
d p i , s t a t e i ∣ s t a t e j = d p i − 1 , s t a t e i ∣ s t a t e j + d p i − 1 , s t a t e j ∗ c n t n u m s i dp_{i,state_i|state_j}=dp_{i-1,state_i|state_j}+dp_{i-1,state_j}*cnt_{nums_i} dpi,stateistatej=dpi1,stateistatej+dpi1,statejcntnumsi
其中 s t a t e i & s t a t e j = 0 state_i\&state_j=0 statei&statej=0。注意特判 s t a t e j = 0 state_j=0 statej=0的情况,即此时只选择第 i i i个数,显然有:
d p i , s t a t e i = d p i − 1 , s t a t e i + c n t n u m s i dp_{i,state_i}=dp_{i-1,state_i}+cnt_{nums_i} dpi,statei=dpi1,statei+cntnumsi
实际 d p dp dp时可以把外围状态优化掉,降低空间复杂度。

class Solution {
public:
    const int mod=1e9+7;
    using ll=long long;
    int qpow(ll a,ll b)
    {
        ll ans=1;
        while(b)
        {
            if(b&1)
                ans=(ans*a)%mod;
            b>>=1;
            a=(a*a)%mod;
        }
        return ans;
    }
    
    int numberOfGoodSubsets(vector<int>& vec) {
        int primes[]={2,3,5,7,11,13,17,19,23,29};
        int primesNum=sizeof(primes)/sizeof(int);
        int idx[31]={0};
        vector<int> factors[31];
        for(int i=0;i<primesNum;i++)
        {
            idx[primes[i]]=i;
            factors[primes[i]].push_back(primes[i]);
        }
        factors[6]={2,3};
        factors[10]={2,5};
        factors[14]={2,7};
        factors[15]={3,5};
        factors[21]={3,7};
        factors[22]={2,11};
        factors[26]={2,13};
        factors[30]={2,3,5};
        int cnt[31]={0};
        for(int ele:vec)
            ++cnt[ele];
        vector<ll> dp(1<<primesNum);
        int maxTimes=0;
        for(int i=2;i<=30;i++)
        {
            if(factors[i].empty()||!cnt[i])
                continue;
            int mask=0;
            for(int facotr:factors[i])
                mask|=1<<idx[facotr];
            dp[mask]=(dp[mask]+cnt[i])%mod;
            maxTimes=max(mask,maxTimes);
            for(int j=1;j<=maxTimes;j++)
            {
                if(j&mask)
                    continue;
                dp[j|mask]=(dp[j|mask]+dp[j]*cnt[i])%mod;
                maxTimes=max(maxTimes,j|mask);
            }
        }
        ll ans=0;
        for(ll val:dp)
            ans=(ans+val)%mod;
        return ans*qpow(2,cnt[1])%mod;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值