当前位置: 首页 > backend >正文

【线段树】3525. 求出数组的 X 值 II|2645

本文涉及知识点

C++线段树

3525. 求出数组的 X 值 II

给你一个由 正整数 组成的数组 nums 和一个 正整数 k。同时给你一个二维数组 queries,其中 queries[i] = [indexi, valuei, starti, xi]。
你可以对 nums 执行 一次 操作,移除 nums 的任意 后缀 ,使得 nums 仍然非空。
给定一个 x,nums 的 x值 定义为执行以上操作后剩余元素的 乘积 除以 k 的 余数 为 x 的方案数。
对于 queries 中的每个查询,你需要执行以下操作,然后确定 xi 对应的 nums 的 x值:
将 nums[indexi] 更新为 valuei。仅这个更改在接下来的所有查询中保留。
移除 前缀 nums[0…(starti - 1)](nums[0…(-1)] 表示 空前缀 )。
返回一个长度为 queries.length 的数组 result,其中 result[i] 是第 i 个查询的答案。
数组的一个 前缀 是从数组开始位置到任意位置的子数组。
数组的一个 后缀 是从数组中任意位置开始直到结束的子数组。
子数组 是数组中一段连续的元素序列。
注意:操作中所选的前缀或后缀可以是 空的 。
注意:x值在本题中与问题 I 有不同的定义。
示例 1:
输入: nums = [1,2,3,4,5], k = 3, queries = [[2,2,0,2],[3,3,3,0],[0,1,0,1]]
输出: [2,2,2]
解释:
对于查询 0,nums 变为 [1, 2, 2, 4, 5] 。移除空前缀后,可选操作包括:
移除后缀 [2, 4, 5] ,nums 变为 [1, 2]。
不移除任何后缀。nums 保持为 [1, 2, 2, 4, 5],乘积为 80,对 3 取余为 2。
对于查询 1,nums 变为 [1, 2, 2, 3, 5] 。移除前缀 [1, 2, 2] 后,可选操作包括:
不移除任何后缀,nums 为 [3, 5]。
移除后缀 [5] ,nums 为 [3]。
对于查询 2,nums 保持为 [1, 2, 2, 3, 5] 。移除空前缀后。可选操作包括:
移除后缀 [2, 2, 3, 5]。nums 为 [1]。
移除后缀 [3, 5]。nums 为 [1, 2, 2]。
示例 2:
输入: nums = [1,2,4,8,16,32], k = 4, queries = [[0,2,0,2],[0,2,0,1]]
输出: [1,0]
解释:
对于查询 0,nums 变为 [2, 2, 4, 8, 16, 32]。唯一可行的操作是:
移除后缀 [2, 4, 8, 16, 32]。
对于查询 1,nums 仍为 [2, 2, 4, 8, 16, 32]。没有任何操作能使余数为 1。
示例 3:
输入: nums = [1,1,2,1,1], k = 2, queries = [[2,1,0,1]]
输出: [5]
提示:
1<=nums[i]<=1091 <= nums[i] <= 10^91<=nums[i]<=109
1<=nums.length<=1051 <= nums.length <= 10^51<=nums.length<=105
1 <= k <= 5
1<=queries.length<=2×1041 <= queries.length <= 2 \times 10^41<=queries.length<=2×104
queries[i] == [indexi, valuei, starti, xi]
0 <= indexi <= nums.length - 1
1<=valuei<=1091 <= valuei <= 10^91<=valuei<=109
0 <= starti <= nums.length - 1
0 <= xi <= k - 1

乘积线段树

线段树类型:单点修改 静态开点。
每个节点(nums[left⋯\cdotsr]包括两个数据:nums[left⋯r]的乘积modKnums[left\cdots r]的乘积 \mod Knums[leftr]的乘积modK,cnt[k]= y的数量。y=Πj=leftrnums[j]modKy=\Pi_{j=left}^r nums[j] \mod Ky=Πj=leftrnums[j]modK
单点修改无需处理缓存。直接更新叶子节点,通过子节点更新父节点。另外要写查询函数。

修改叶子节点

mul = xmodK,cnt[0⋯K−1]=0,cnt[mul]=1x \mod K,cnt[0\cdots K-1]=0,cnt[mul]=1xmodK,cnt[0K1]=0,cnt[mul]=1

更新父节点

父节点的mul = 子节点的mul modK\mod KmodK
父节点的cnt[i]= 子节点cnt[i]之和。

查询

mul =1
依次查询各节点:
如果mul是0,则结果一定是0,如果xix_ixi是0,ans += (r -left)+1 ,continue;
ans += cnt[xi÷mulx_i \div mulxi÷mul] 注意:逆元可以提前算好。
mul =mul ×\times× 当前节点的mul。

代码

核心代码

		template<long long MOD = 1000000007,class T1 = int, class T2 = long long>
class C1097Int
{
public:C1097Int(T1 iData = 0) :m_iData(iData% MOD){}C1097Int(T2 llData) :m_iData(llData% MOD) {}C1097Int  operator+(const C1097Int& o)const{return C1097Int(((T2)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((T2)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = ((T2)MOD + m_iData - o.m_iData) % MOD;return *this;}C1097Int  operator-(const C1097Int& o)const{return C1097Int(((T2)MOD + m_iData - o.m_iData) % MOD);}C1097Int  operator*(const C1097Int& o)const{return((T2)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((T2)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(T2 n)const{C1097Int iRet = (T1)1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}T1 ToInt()const{return ((T2)m_iData + MOD) % MOD;}
private:T1 m_iData = 0;;
};
template<class TSave, class TRecord >
class CSingeUpdateLineTree
{
protected:virtual void OnQuery(TSave& ans,const TSave& cur, const int& iSaveLeft, const int& iSaveRight) = 0;virtual void OnUpdate(TSave& save, int iSaveIndex, const TRecord& update) = 0;virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) = 0;
};template<class TSave, class TRecord >
class CVectorSingUpdateLineTree : public CSingeUpdateLineTree<TSave, TRecord>
{
public:CVectorSingUpdateLineTree(int iEleSize, TSave tDefault) :m_iEleSize(iEleSize),m_save(iEleSize*4,tDefault), m_tDefault(tDefault){}void Update(int index, TRecord update) {Update(1, 0, m_iEleSize-1, index, update);}TSave Query(int leftIndex, int leftRight,TSave tDefault) {TSave ans = tDefault;Query(ans,1, 0, m_iEleSize - 1, leftIndex, leftRight);return ans;}TSave Query(int leftIndex, int leftRight) {		return Query(leftIndex,leftRight, m_tDefault);}void Init(std::function<void(TSave&,const int&)> fun) {Init(fun,1, 0, m_iEleSize - 1);}TSave QueryAll() {return m_save[1];}
protected:int m_iEleSize;void Init(std::function<void(TSave&, const int&)> fun,int iNodeNO, int iSaveLeft, int iSaveRight){if (iSaveLeft == iSaveRight) {fun(m_save[iNodeNO], iSaveLeft);return;}const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;Init(fun,iNodeNO * 2, iSaveLeft, mid);Init(fun,iNodeNO * 2 + 1, mid + 1, iSaveRight);this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO*2], m_save[iNodeNO*2+1], iSaveLeft, iSaveRight);}void Query(TSave& ans,int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) {if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) {this->OnQuery(ans,m_save[iNodeNO],iSaveLeft,iSaveRight);return;}if (iSaveLeft == iSaveRight) {//没有子节点return;}const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;if (mid >= iQueryLeft) {Query(ans,iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight);}if (mid + 1 <= iQueryRight) {Query(ans,iNodeNO * 2 + 1, mid + 1, iSaveRight, iQueryLeft, iQueryRight);}}void Update(int iNodeNO, int iSaveLeft, int iSaveRight, int iUpdateNO, TRecord update) {if (iSaveLeft == iSaveRight){this->OnUpdate(m_save[iNodeNO], iSaveLeft, update);return;}const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;if (iUpdateNO <= mid) {Update(iNodeNO * 2, iSaveLeft, mid, iUpdateNO, update);}else {Update(iNodeNO * 2 + 1, mid + 1, iSaveRight, iUpdateNO, update);}this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO*2], m_save[iNodeNO*2+1], iSaveLeft, iSaveRight);}vector<TSave> m_save;const TSave m_tDefault;
};typedef	C1097Int<> BI;class CData{public:CData() {}int mul = 1;int cnt[5] = { 0 };};template<class TSave = CData, class TRecord = int>class  CMyLineTree : public CVectorSingUpdateLineTree<TSave, TRecord>{public:CMyLineTree(int iEleSize, int K,TSave tDefault):m_iK(K), CVectorSingUpdateLineTree<TSave, TRecord>(iEleSize,tDefault) {}		protected:			virtual void OnQuery(TSave& ans, const TSave& cur, const int& iSaveLeft, const int& iSaveRight) {Union(ans, ans, cur);}virtual void OnUpdate(TSave& save, int iSave, const TRecord& updatee) {save.mul = updatee % m_iK;memset(save.cnt, 0, sizeof(save.cnt));save.cnt[save.mul] = 1;}virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) {Union(par, left, r);}void Union(TSave& par, const TSave& left, const TSave& r) {		memcpy(par.cnt, left.cnt, sizeof(par.cnt));for (int i = 0;i < m_iK;i++) {const int i1 = (i * left.mul) % m_iK;par.cnt[i1] += r.cnt[i];}par.mul = left.mul * r.mul % m_iK;}const int m_iK;};class Solution {public:vector<int> resultArray(vector<int>& nums, int K, vector<vector<int>>& queries) {const int N = nums.size();CMyLineTree lineTree(N, K , CData());for (int i = 0;i < N;i++) {lineTree.Update(i, nums[i]);}vector<int> ans;for (const auto& v : queries) {lineTree.Update(v[0],v[1]);nums[v[0]] = v[1];auto curAns = lineTree.Query(v[2], N - 1);ans.emplace_back(curAns.cnt[v[3]]);}return ans;}};

单元测试

vector<int> nums;int k;vector<vector<int>> queries;TEST_METHOD(TestMethod01){nums = { 1,2,3,4,5 }, k = 3, queries = { {2,2,0,2},{3,3,3,0},{0,1,0,1} };auto res = Solution().resultArray(nums, k,queries);AssertEx({ 2,2,2 }, res);}TEST_METHOD(TestMethod02){nums = { 1,2,4,8,16,32 }, k = 4, queries = { {0,2,0,2},{0,2,0,1} };auto res = Solution().resultArray(nums, k, queries);AssertEx({ 1,0 }, res);}TEST_METHOD(TestMethod03){nums = { 1,1,2,1,1 }, k = 2, queries = { {2,1,0,1} };auto res = Solution().resultArray(nums, k, queries);AssertEx({ 5 }, res);}

后缀积 线段树(放弃:错误或过于复杂想法)

性质一:suff[n]表示nums长度为n的后缀的乘积。则nums删除长度为n1的前缀和长度为n2的后缀后为num1,则num1的乘积为suff[N−n1]÷suff[n2],0≠suff[n2]suff[N-n1]\div suff[n2],0 \neq suff[n2]suff[Nn1]÷suff[n2],0=suff[n2]
性质二:如果符合性质一,则n1 = starti,0≤n2<N−n1start_i,0 \le n2 < N-n1starti,0n2<Nn1,则判断suff[N−n1]÷suff[n2]==y,y≡ximodKsuff[N-n1] \div suff[n2] == y,y \equiv x_i \mod Ksuff[Nn1]÷suff[n2]==y,yximodK。即suff[n2]=z=suff[N−n1]÷xisuff[n2]= z = suff[N-n1]\div x_isuff[n2]=z=suff[Nn1]÷xi,即suff[n2]中为xix_ixi的数量。
性质三:如果nums[i]是0,则nums[i⋯j\cdots jj]的乘积一定是0,j≥ij \ge iji

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步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/19713.html

相关文章:

  • Spring 事务原理解析:AOP 的一次完美落地
  • 深度学习——基于卷积神经网络实现食物图像分类【4】(使用最优模型)
  • 广度优先搜索(BFS, Breadth-First Search)
  • 数字化转型的六大天问:你的项目为何失败
  • 数据开发工作了一年,准备跳槽,回顾一些面试常见问题,大数据面试题汇总与答案分享
  • 【3D打印】3D打印机首次使用心得
  • 2025最新“Java 面试八股文 + 各大厂的面试真题”限时开源
  • 人工智能助力流感疫苗选择:MIT 团队推出 VaxSeer 系统
  • Understanding the Flap T in American English
  • 开源企业级快速开发平台(JeecgBoot)
  • Python闭包机制:原理、应用与安全防护
  • 【Doris入门】Doris数据表模型:聚合模型(Aggregate Key Model)详解
  • java-设计模式-4-创建型模式-工厂
  • 【52页PPT】服务业数字化转型如何做(附下载方式)
  • Ubuntu 用户和用户组
  • X86、X64 与 ARM:架构的剖析与比较
  • webpack性能优化指南
  • MacOS - 记录MacOS发烫的好几天 - 幕后黑手竟然是
  • 神经网络|(十八)概率论基础知识-伽马函数溯源-阶乘的积分表达式
  • k8s常用命令
  • 对矩阵行化简操作几何含义的理解
  • HDI是什么?与普通线路板有何区别?优势在哪?
  • 嵌入式git分支管理策略
  • Java基础第9天总结(可变参数、Collections、斗地主)
  • 魔域服务器多少钱一个月?魔域服务器配置要求及推荐
  • Linux 入门到精通,真的不用背命令!零基础小白靠「场景化学习法」,3 个月拿下运维 offer,第二十四天
  • 鸿蒙Next开发指南:XComponent与Progress组件的深度解析与实践
  • 在 PySpark 中解锁窗口函数的力量,实现高级数据转换
  • 数控机床相邻轨迹最大过渡速度计算方法介绍
  • 【Kubernetes】知识点2