【数论 状态机dp】2572. 无平方子集计数
本文涉及知识点
C++动态规划
数论 质数、最大公约数、菲蜀定理
LeetCode 2572. 无平方子集计数
给你一个正整数数组 nums 。
如果数组 nums 的子集中的元素乘积是一个 无平方因子数 ,则认为该子集是一个 无平方 子集。
无平方因子数 是无法被除 1 之外任何平方数整除的数字。
返回数组 nums 中 无平方 且 非空 的子集数目。因为答案可能很大,返回对 109 + 7 取余的结果。
nums 的 非空子集 是可以由删除 nums 中一些元素(可以不删除,但不能全部删除)得到的一个数组。如果构成两个子集时选择删除的下标不同,则认为这两个子集不同。
示例 1:
输入:nums = [3,4,4,5]
输出:3
解释:示例中有 3 个无平方子集:
- 由第 0 个元素 [3] 组成的子集。其元素的乘积是 3 ,这是一个无平方因子数。
- 由第 3 个元素 [5] 组成的子集。其元素的乘积是 5 ,这是一个无平方因子数。
- 由第 0 个和第 3 个元素 [3,5] 组成的子集。其元素的乘积是 15 ,这是一个无平方因子数。
可以证明给定数组中不存在超过 3 个无平方子集。
示例 2:
输入:nums = [1]
输出:1
解释:示例中有 1 个无平方子集: - 由第 0 个元素 [1] 组成的子集。其元素的乘积是 1 ,这是一个无平方因子数。
可以证明给定数组中不存在超过 1 个无平方子集。
提示:
1 <= nums.length <= 1000
1 <= nums[i] <= 30
动态规划
预处理
v 记录[1,30]的质数。m = v.size()
cnt[i]统计i出现的次数 ,i$\in[0,30]
vMask[i]:记录i包括质因数的情况
invalid[i]:记录i的因数是否有平方数
动态规划的状态表示
dp‘[i][maks] 表示最大数<=i,且包括质数的状态为mask的数量。 (1 << j )& mask 表示乘积已经包括v[j]
i
∈
\in
∈[2,30] i==1最后做特殊处理。
pre = dp’[i] dp = dp’[i+1]
使用滚动数组,空间复杂度:O(2m)
动态规划的转移方程
每个前置状态都只有两种变化:选择不选择。
选择需要判断:n*p 是否是 v的某个元素的平方。判断方式:vMask[i]&p
单个状态转移方程的时间复杂度是:O(1)
总时间复杂度:O(30
×
\times
× 2m)
动态规划的初始值
pre[0]=1,其它全部为0
动态规划的填表顺序
i = 2 to 30
忽略invalid[i]
动态规划的返回值
sum =
∑
\sum
∑(pre)
sum 的任意状态都可以选择任意个1,也可以不选择。
如果只有1,除选择0个1外,可以任意选择1。故结果:
(sum+1)
×
\times
× 2cnt[1]-1
代码
核心代码
class CUniqueFactorization
{
public:
CUniqueFactorization(int iMax) {
int iMaxSqrt = sqrt(iMax) + 2;
m_vPrime = CreatePrime(iMaxSqrt);
}
void Factorization(int x) {
m_a.clear();
m_n.clear();
for (const auto& iPre : m_vPrime) {
int cnt = 0;
while (0 == x % iPre) {
cnt++;
x /= iPre;
}
if (cnt > 0) {
m_a.emplace_back(iPre);
m_n.emplace_back(cnt);
}
}
if (x > 1) {
m_a.emplace_back(x);
m_n.emplace_back(1);
}
}
vector<int> m_a, m_n;
vector<int> CreatePrimeOld(int iMax)
{
vector<int> vNo(iMax + 1);
vector<int> vPrime;
for (int i = 2; i <= iMax; i++)
{
if (!vNo[i])
{
vPrime.emplace_back(i);
}
for (const auto& n : vPrime)
{
if (n * i > iMax)
{
break;
}
vNo[n * i] = true;
}
}
return vPrime;
}
static vector<int> CreatePrime(int iMax)
{
vector<bool> isPrime(iMax + 1, true);
vector<int> vPrime;
for (int i = 2; i <= iMax; i++)
{
if (isPrime[i])
{
vPrime.emplace_back(i);
}
for (const auto& n : vPrime)
{
if (n * i > iMax) { break; }
isPrime[n * i] = false;
if (0 == i % n) { break; }
}
}
return vPrime;
}
vector<int> m_vPrime;
};
template<int MOD = 1000000007>
class C1097Int
{
public:
C1097Int(long long llData = 0) :m_iData(llData% MOD)
{
}
C1097Int operator+(const C1097Int& o)const
{
return C1097Int(((long long)m_iData + o.m_iData) % MOD);
}
C1097Int& operator+=(const C1097Int& o)
{
m_iData = ((long long)m_iData + o.m_iData) % MOD;
return *this;
}
C1097Int& operator-=(const C1097Int& o)
{
m_iData = (m_iData + MOD - o.m_iData) % MOD;
return *this;
}
C1097Int operator-(const C1097Int& o)
{
return C1097Int((m_iData + MOD - o.m_iData) % MOD);
}
C1097Int operator*(const C1097Int& o)const
{
return((long long)m_iData * o.m_iData) % MOD;
}
C1097Int& operator*=(const C1097Int& o)
{
m_iData = ((long long)m_iData * o.m_iData) % MOD;
return *this;
}
C1097Int operator/(const C1097Int& o)const
{
return *this * o.PowNegative1();
}
C1097Int& operator/=(const C1097Int& o)
{
*this /= o.PowNegative1();
return *this;
}
bool operator==(const C1097Int& o)const
{
return m_iData == o.m_iData;
}
bool operator<(const C1097Int& o)const
{
return m_iData < o.m_iData;
}
C1097Int pow(long long n)const
{
C1097Int iRet = 1, iCur = *this;
while (n)
{
if (n & 1)
{
iRet *= iCur;
}
iCur *= iCur;
n >>= 1;
}
return iRet;
}
C1097Int PowNegative1()const
{
return pow(MOD - 2);
}
int ToInt()const
{
return m_iData;
}
private:
int m_iData = 0;;
};class Solution {
public:
int squareFreeSubsets(vector<int>& nums) {
auto v = CUniqueFactorization::CreatePrime(30);
int cnt[31] = { 0 };
for (const auto& n : nums) {
cnt[n]++;
}
int amask[31] = { 0 };
bool invalid[31] = { false };
for (int i = 2; i <= 30; i++) {
for (int j = 0; j < v.size(); j++) {
if (0 != i % v[j]) { continue; }
amask[i] |= (1 << j);
if (0 == i % (v[j] * v[j])) {
invalid[i] = true;
}
}
}
vector<C1097Int<>> pre(1 << v.size());
pre[0] = 1;
for (int i = 2; i <= 30; i++) {
if (invalid[i]) { continue; }
vector<C1097Int<>> dp = pre;
for (int p = 0; p < pre.size(); p++) {
if (p & amask[i]) { continue; }
dp[p | amask[i]] += pre[p] * cnt[i];
}
pre.swap(dp);
}
C1097Int<> sum = accumulate(pre.begin()+1, pre.end(), C1097Int<>());
C1097Int<> ret = (sum + 1) * C1097Int<>(2).pow(cnt[1]) - 1;
return ret.ToInt();
}
};
单元测试
class Solution {
public:
int squareFreeSubsets(vector<int>& nums) {
auto v = CUniqueFactorization::CreatePrime(30);
int cnt[31] = { 0 };
for (const auto& n : nums) {
cnt[n]++;
}
int amask[31] = { 0 };
bool invalid[31] = { false };
for (int i = 2; i <= 30; i++) {
for (int j = 0; j < v.size(); j++) {
if (0 != i % v[j]) { continue; }
amask[i] |= (1 << j);
if (0 == i % (v[j] * v[j])) {
invalid[i] = true;
}
}
}
vector<C1097Int<>> pre(1 << v.size());
pre[0] = 1;
for (int i = 2; i <= 30; i++) {
if (invalid[i]) { continue; }
vector<C1097Int<>> dp = pre;
for (int p = 0; p < pre.size(); p++) {
if (p & amask[i]) { continue; }
dp[p | amask[i]] += pre[p] * cnt[i];
}
pre.swap(dp);
}
C1097Int<> sum = accumulate(pre.begin()+1, pre.end(), C1097Int<>());
C1097Int<> ret = (sum + 1) * C1097Int<>(2).pow(cnt[1]) - 1;
return ret.ToInt();
}
};
template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
Assert::AreEqual(t1, t2);
}
void AssertEx( double t1, double t2)
{
auto str = std::to_wstring(t1) + std::wstring(1,32) + std::to_wstring(t2);
Assert::IsTrue(abs(t1 - t2) < 1e-5,str.c_str() );
}
template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
Assert::AreEqual(v1.size(), v2.size());
for (int i = 0; i < v1.size(); i++)
{
Assert::AreEqual(v1[i], v2[i]);
}
}
template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
sort(vv1.begin(), vv1.end());
sort(vv2.begin(), vv2.end());
Assert::AreEqual(vv1.size(), vv2.size());
for (int i = 0; i < vv1.size(); i++)
{
AssertEx(vv1[i], vv2[i]);
}
}
namespace UnitTest
{
vector<int> nums;
TEST_CLASS(UnitTest)
{
public:
TEST_METHOD(TestMethod00)
{
nums = { 3, 4, 4, 5 };
auto res = Solution().squareFreeSubsets(nums);
AssertEx(3, res);
}
TEST_METHOD(TestMethod01)
{
nums = { 1 };
auto res = Solution().squareFreeSubsets(nums);
AssertEx(1, res);
}
};
}
扩展阅读
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关推荐
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。