个人Limitの线段树题单题解主目录:Limitの线段树题单 题解目录_HeartFireY的博客-CSDN博客
给定序列,要求不带修支持在线查询区间最小公倍数对1e9+7取模的结果
主席树不带修在线维护区间GCD,记录质因数最后一次出现的位置并消除贡献
洛谷传送门:CF1422F Boring Queries - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
CF传送门:F. Boring Queries (codeforces.com)
题目分析给定序列,要求支持在线查询区间最小公倍数对 1 e 9 + 7 1e9+7 1e9+7取模的结果,不带修。
考虑暴力求解的方式:直接合并最小公倍数,但是发现带取模,无法合并。
但是发现最大公倍数可以转化为求区间乘积除区间最大公因数。那么可以想到用区间合并的方式求解,因为最小公倍数时可以两两区间进行合并求出的。但是带取模,我们会发现由于出现了带模意义下的除法,不能简单的进行合并。
首先我们写出区间最小公倍数的表达式: a n s ( l , r ) = ∏ i = l r a i gcd i = l r a i ans(l ,r) = \frac{\prod_{i = l}^{r}a_i}{\gcd_{i = l}^{r}a_i} ans(l,r)=gcdi=lrai∏i=lrai 再转化为模意义下的表达式: a n s ( l , r ) m o d x = ∏ i = l r a i × ( gcd i = l r a i ) − 1 ans(l ,r)\mod x =\prod_{i = l}^{r}a_i \times ({\gcd_{i = l}^{r}a_i})^{-1} ans(l,r)modx=i=l∏rai×(i=lgcdrai)−1 我们都知道,对于 gcd i = l r a i {\gcd_{i = l}^{r}a_i} gcdi=lrai,我们可以对 a i ( 1 ≤ i ≤ n ) a_i(1 \leq i \leq n) ai(1≤i≤n)分解质因数,然后求公有因数连乘积,注意共有因数是有重复的。
我们可以将区间公有质因数表示为数对 ( p i , t i ) (p_i, t_i) (pi,ti), p i p_i pi表示质因数, t i t_i ti表示在共有质因数中该因数的出现次数。那么将共有质因数写作集合 { ( p 1 , t 1 ) , ( p 2 , t 2 ) , … , ( p i , t i ) } \{(p_1, t_1), (p_2, t_2), \dots,(p_i, t_i)\} {(p1,t1),(p2,t2),…,(pi,ti)},那么可以将区间最大公因数写作: gcd i = l r a i = p 1 t 1 × p 2 t 2 × ⋯ × p i t i × … {\gcd_{i = l}^{r}a_i} = p_1^{t_1} \times p_2^{t_2} \times \dots\times p_i^{t_i} \times \dots i=lgcdrai=p1t1×p2t2×⋯×piti×… 现在考虑加入一个数字的影响,假设现在有一个长度为 2 2 2的序列 a 1 , a 2 , a 3 {a_1, a_2, a_3} a1,a2,a3,设: a 1 = p 1 a 2 = p 1 ∗ p 1 ∗ p 1 a 3 = p 1 ∗ p 1 ∗ p 1 ∗ p 1 ∗ p 1 a_1 = p_1\\ a_2 = p_1 * p_1 * p_1\\ a_3 = p_ 1 * p_1 * p_1 * p_1 * p_1 a1=p1a2=p1∗p1∗p1a3=p1∗p1∗p1∗p1∗p1 那么对于单点 1 1 1,答案就是 p 1 p_1 p1,但是对于区间 [ 1 , 2 ] [1, 2] [1,2],我们可以发现要除 p 1 p_1 p1(最小公倍数式子)。那么考虑如何消除多乘的贡献。一个很妙的思路是,固定右端点进行枚举,我们维护质因子的 k k k次幂在左边最后一次出现的位置,每次插入单点时,边分解质因数,边对先前重叠的贡献(即为区间 G C D GCD GCD的组成部分)进行消除。
我们分析上述序列的区间 [ 1 , 2 ] [1,2] [1,2]的计算过程,为了方便解释,设 p 1 = 2 p_1 = 2 p1=2:
- 单点 a [ 1 ] a[1] a[1]插入 1 1 1叶节点, r o o t [ 1 ] = 1 root[1] = 1 root[1]=1, 1 1 1号叶子节点上有 2 2 2的贡献,故 l s t [ 2 ] = 1 lst[2] = 1 lst[2]=1
- a [ 2 ] a[2] a[2]不是质数,质因数分解,第一次分解到达 2 ∗ 2 = 4 2 * 2 = 4 2∗2=4,发现之前没有重复贡献 ( l s t [ 4 ] = 0 ) (lst[4] = 0) (lst[4]=0),不需要消除
- a [ 2 ] a[2] a[2]变为 4 4 4仍不是质数,质因数分解,第二次分解到达 2 2 2,发现之前有重复贡献 l s t [ 1 ] lst[1] lst[1],于是对 1 1 1号叶子节点乘 2 − 1 2^{-1} 2−1消除贡献
- 单点插入 a [ 2 ] a[2] a[2]节点到 2 2 2号叶子节点上, 2 2 2号叶子节点上叠加 8 8 8的贡献
我们可以发现,最终答案就是各叶子节点的累乘。即我们通过对区间 G C D GCD GCD分解进行消除达到了消除重复贡献的效果。
类似的,我们把 a 1 a_1 a1和 a 2 a_2 a2交换一下,分析插入顺序,发现单个 p 1 p_1 p1造成的重复贡献在插入 a 2 a_2 a2时仍会被消除。
但是我们发现,这样没法查询任意区间。因为消除贡献的计算会导致叶节点发生变化。但是我们发现有一个东西很符合我们想要的效果:固定右端点查询区间,每加入 1 1 1个右端点就会构成新序列,但与先前的序列有较大重叠。那么显然我们可以用可持久化权值线段树维护这个东西。
在质因数分解的时候,记录质因数幂次最后出现的位置,并判断消除先前的贡献。分解完后加入单点的分子乘积贡献。查询时 [ l , r ] [l, r] [l,r]从 r o o t [ r ] root[r] root[r]进入查询即可。这里有一点很重要的:当你消除上个数的贡献的时,上个数的之前不会再出现对上个数的重复贡献,也就是每个新插入右端点的重复贡献仅与上次质因子幂次出现的位置有关。
由于开点次数很多,因此很卡空间。如果用空间可回收的版本可能效果会更好。
Code#include
#pragma gcc optimize("O2")
#pragma g++ optimize("O2")
// #define int long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 10, MOD = 1e9 + 7;
namespace ffastIO {
const int bufl = 1
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?