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}
sum∗2cnt。总复杂度为
O
(
n
∗
2
n
)
O(n*2^{n})
O(n∗2n),其中
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(18∗210)。转移方程有为:
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,statei∣statej=dpi−1,statei∣statej+dpi−1,statej∗cntnumsi
其中
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=dpi−1,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;
}
};