【数论 状态机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++**实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1522463.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构,单向链表

数据结构是计算机科学中的一个核心概念&#xff0c;它研究的是数据的组织、管理和存储方式&#xff0c;以及在这些数据上进行操作时的算法。数据结构为数据的高效访问和修改提供了基础。 数组&#xff08;Array&#xff09;&#xff1a;一种线性数据结构&#xff0c;可以存储固…

开源 AI 智能名片 O2O 商城小程序:引入淘汰机制,激发社交电商新活力

摘要&#xff1a;本文深入探讨在社交电商领域中&#xff0c;开源 AI 智能名片 O2O 商城小程序如何通过设置淘汰机制&#xff0c;实现“良币驱逐劣币”&#xff0c;激励士气&#xff0c;为社交电商企业注入新的活力。通过分析缺乏淘汰机制的弊端以及设置淘汰机制的优势&#xff…

一篇入门C语言【文件】

本科期间C语言的课本无论哪个版本都会有【文件】这一章节&#xff0c;不过好多学校基本上不讲或者就出一道选择题&#xff0c;讲得很浅&#xff0c;今天这篇详细总结一下这部分的知识~ 一.原理解析 文件是指存在在外部介质&#xff08;如磁盘、磁带&#xff09;上的数据集合。操…

NPDP|如何在传统行业中做好产品管理的策略与建议

在当今这个快速变化的数字时代&#xff0c;传统行业面临着前所未有的挑战与机遇。产品管理作为连接市场需求与企业生产的核心环节&#xff0c;其重要性不言而喻。对于传统行业而言&#xff0c;做好产品管理不仅意味着保持竞争力&#xff0c;更是实现转型升级、拥抱未来的关键。…

【类模板】成员函数模板

一、成员函数模板的基本含义 不管是普通的类&#xff0c;还是类模板&#xff0c;都可以为其定义成员函数模板&#xff0c;以下的情况就是类模板和成员函数模板都有各自独立的一套参数&#xff1a; template<typename T1> class A { public:T1 m_ic;static constexpr int…

0.3 学习Stm32经历过的磨难

文章目录 用库函数传参 能否按位或STM32库函数XXX_GetFlagStatus和XXX_GetITStatus的区别 用库函数传参 能否按位或 答案是看清况&#xff0c;而不是一股脑的写&#xff01;&#xff08;血泪的经验啊&#xff09; 可行的情况&#xff1a; //如gpio初始化结构体中的gpiopin参…

c++list

list介绍 list是序列容器&#xff0c;允许对序列中任意位置的恒定时间插入和擦除操作&#xff0c;以及双向迭代。 list容器被实现为双向链表;双向链表可以将它们包含的每个元素存储在不同且不相关的存储位置。 list的使用 list中的接口比较多&#xff0c;此处类似&#xff0…

RedisStack十部曲之二:Redis的核心概念

文章目录 键空间修改和查询键键过期遍历键空间 客户端缓存在计算机科学中有两个难题客户端缓存的Redis实现跟踪模式的工作机制统一的键命名空间 两种连接方式缓存策略Opt-in 模式Opt-out 模式广播模式NOLOOP选项避免竟态条件当与服务器失去连接怎么办什么值得缓存 流水线请求/响…

使用QTcpSocket在两台ubuntu之间实现通讯

重点提取&#xff1a; 1.保证服务端和客户端端口号一致 2.保证服务端和客户端在同一网段(可以通过网线连接) 3保证客户端界面输入的ip是服务段的ip 实现步骤&#xff1a; 首先&#xff0c;构造服务端界面和客户端界面如下 服务端界面 客户端界面 其次具体代码 在.pro文件…

FRP内网穿透与神卓互联,优势对比

本文介绍分析了当前市面上两款常用的内网穿透工具 frp内网穿透介绍 一、概述 frp&#xff08;Fast Reverse Proxy&#xff09;是一款高性能的反向代理应用&#xff0c;主要用于实现内网穿透功能。通过frp&#xff0c;用户可以将内网中的服务器或服务暴露到公网上&#xff0c;…

【ACM独立出版|EI快检索-高录用|IEEE Fellow支持】2024年数字经济与计算机科学国际学术会议(DECS2024)

【ACM独立出版&#xff5c;EI快检索-高录用&#xff5c;IEEE Fellow支持】 2024年数字经济与计算机科学国际学术会议&#xff08;DECS2024&#xff09; *ACM独立出版&#xff0c;快检索&#xff0c;高录用 *见刊后1个月左右完成EI&Scopus检索 *国内211大学、世界QS名校…

#驱动开发

内核模块 字符设备驱动 中断、内核定时器 裸机开发和驱动开发的区别&#xff1f; 裸机开发 驱动开发&#xff08;基于内核&#xff09; 相同点 都能够控制硬件&#xff08;本质&#xff1a;操作寄存器&#xff09; 不同点 用C语言给对应的地址里面写值 按照一定的框架格式…

hackme靶机通关攻略

1、登录靶机&#xff0c;查询是否有注入点 2、判断闭合方式 输入OSINT and 11 # 输入OSINT and 12 # 得出闭合方式为单引号 2、查询数据库名 输入-1 union select database(),2,3 # 3、查询数据库中的表 输入-1 union select group_concat(table_name),2,3 from informa…

还在用谷歌翻译?这4款翻译工具也许更高效!

随着国内很多翻译工具的不断发展&#xff0c;谷歌翻译相对来说不是一款十分有优势的翻译工具。并且使用的时候还会受到网络的限制&#xff0c;如过大家有翻译方面的需求的话&#xff0c;不妨试试这几款翻译工具。不论是从翻译的语言种类&#xff0c;翻译质量还是翻译速度来看&a…

金蝶云星空协同平台业务对象下同时存在未加载未引入对象的原因分析和处理方式

文章目录 问题截图原因分析&#xff0c;解决方式 问题截图 原因分析&#xff0c;解决方式 未加载是 别的账套提交的数据&#xff0c;本账套不存在&#xff0c;点击加载则回、会同步到当前数据中心 未引入&#xff0c;则是在A账套删除后提交到应用&#xff0c;在B账套则显示未…

我的推荐:腾讯云罗云《从零构建向量数据库》

在2024年8月&#xff0c;好几本和数据库相关的图书相继出版&#xff0c;我以为&#xff0c;这恰恰是数据库领域蓬勃向上的一种表现。 数据库需要更多的人关注&#xff0c;哪怕是谈论&#xff0c;所以我的《数据库简史》是一种尝试&#xff0c;希望以一种科普的风格&#xff0c;…

陪诊志愿服务正在开展,喜鹊医疗打造国内首家陪诊聚合平台

2024年8月&#xff0c;为了培养一支专业、合格的陪诊志愿服务队伍&#xff0c;为志愿者提供就业帮扶&#xff0c;也满足社会日益增长的健康需求。由喜鹊医疗捐赠专项资金&#xff0c;中国民族卫生协会联合中国志愿基金会共同开展“健康中国行&#xff0c;陪诊惠民工程——陪诊志…

暴力破解和撞库攻击有什么区别,怎么防御暴力破解和撞库攻击

在网络世界中&#xff0c;我们的账户安全时刻面临着各种威胁。其中&#xff0c;暴力破解和撞库攻击就是常见的两种危险手段。今天&#xff0c;就让我们深入了解这两种攻击方式的含义&#xff0c;并学习如何有效地进行防护。 暴力破解的含义 暴力破解&#xff0c;就如同一个不…

FPGA开发:EDA × HDL × IP核

EDA技术 EDA指Electronic Design Automation&#xff0c;翻译为&#xff1a;电子设计自动化&#xff0c;最早发源于美国的影像技术&#xff0c;主要应用于集成电路设计、FPGA应用、IC设计制造、PCB设计上面。 而EDA技术就是指以计算机为工具&#xff0c;设计者在EDA软件平台上…

s3fs的使用

s3fs是一个将s3服务器上的桶映射为本地目录的程序。 项目源码位于&#xff1a; https://github.com/s3fs-fuse/s3fs-fuse 这是一个比较长期的项目了&#xff0c;现在在大数据领域S3协议基本上已经成为最通用的协议。 各大云平台&#xff0c;什么阿里云&#xff0c;某为云&am…