【数论 状态机dp】2572. 无平方子集计数

ops/2024/10/9 0:50:05/

本文涉及知识点

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++**实现。


http://www.ppmy.cn/ops/105306.html

相关文章

Docker通信全视角:原理、实践与技术洞察

一、引言 在云计算和微服务架构日益成熟的今天&#xff0c;Docker作为一种轻量级的容器化技术&#xff0c;已成为现代软件开发和部署的关键组件。Docker容器通过为应用程序提供隔离的运行环境&#xff0c;不仅显著提升了部署效率&#xff0c;而且增强了系统的可移植性和安全性。…

git中的分支是什么?分支有哪些好处?如何建立分支?

git中的分支是什么&#xff1f; 在Git中&#xff0c;分支是版本库中记录版本位置&#xff08;支线&#xff09;的一种方式。分支可以被视为一条时间线&#xff0c;每次提交都会在这条时间线上形成一个新的版本。通过分支&#xff0c;开发者可以在不影响主线&#xff08;通常是…

TS 学习(一)

如果我们在 ts 中写 不用运行就能在文件中报错 ts 是一种静态类型的检查 能将运行时出现的错误前置 一般不用 命令行编译 ts 转换成 js 将中文转码 tsc index&#xff08;.ts&#xff09; 输入命令生成 配置文件 能在中间进行 配置转换成 js 的哪个规范 es5 还是 6 和其它转…

Java12 Excel和Json文件解析

Excel文件解析&#xff1a; Excel文件解析(EasyExcel框架解析) Excel文件解析(Apache POl框架解析) &#xff08;1&#xff09;Excel文件对象创建&#xff1a;POI 《1》创建工作簿对象: XSSFWorkbook workbooknew XSSFWorkbook&#xff08;&#xff09;&#xff1b; 《2》创…

Swift 可选类型

Swift 可选类型 Swift 是一种强类型编程语言,它在类型安全方面做了很多工作,以确保代码的稳定性和可靠性。在 Swift 中,可选类型(Optional)是一种特殊的类型,用于处理值可能缺失的情况。本文将详细介绍 Swift 中的可选类型,包括其定义、使用场景、语法以及如何正确地处…

跨vue、react、angular框架渲染

应用场景 A 框架项目渐进式迁移到 B 框架一码多框架&#xff0c;开发一套组件&#xff0c;各个框架的应用复用&#xff1b;微前端实现【无沙箱能力】 思路 在 A 框架中渲染 B 框架的组件&#xff0c;将 B 框架的组件渲染成真实 dom 再挂载到对应位置。 实现demo 待补充&am…

SPP/SPPF/Focal Module

一、在图像的分类任务重&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一般含有5层&#xff1a; 输入层卷积层激活层池化层全连接层 全连接层通常要求输入为一维向量。在CNN中&#xff0c;卷积层和池化层的输出特征图会被展平&#xff08;flatten&#xff09;为一维…

Matlab 并联双振子声子晶体梁结构带隙特性研究

参考文献&#xff1a;吴旭东,左曙光,倪天心,等.并联双振子声子晶体梁结构带隙特性研究[J].振动工程学报,2017,30(01):79-85. 为使声子晶体结构实现范围更宽的多带隙特性&#xff0c;基于单振子型声子晶体结构弯曲振动带隙频率范围窄的局 限&#xff0c;提出了一种双侧振子布置…