[线段树 差分 区间转单点]区间最大公约数
Posted 鱼竿钓鱼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[线段树 差分 区间转单点]区间最大公约数相关的知识,希望对你有一定的参考价值。
[线段树 差分]区间最大公约数
思路
操作1:区间[L,R]增加一个数
操作2:求区间最大公约数
g
c
d
(
a
[
l
]
,
a
[
l
+
1
]
,
a
[
l
+
2
]
…
…
a
[
r
]
)
gcd(a[l],a[l+1],a[l+2]……a[r])
gcd(a[l],a[l+1],a[l+2]……a[r])
首先思考维护哪些信息
struct Node{
int l,r;
LL g;//gcd
}
只是查询的话已经够了,接下来思考修改怎么办。
如果是做区间修改的话很麻烦,我们可以利用差分把区间修改转为单点修改。
(
x
,
y
,
z
)
=
(
x
,
y
−
x
,
z
−
y
)
(x,y,z)=(x,y-x,z-y)
(x,y,z)=(x,y−x,z−y)
(
a
1
,
a
2
,
…
…
a
n
)
=
(
a
1
,
a
2
−
a
1
,
a
3
−
a
2
,
…
…
a
n
−
a
n
−
1
)
(a_1,a_2,……a_n)=(a_1,a_2-a_1,a_3-a_2,……a_n-a_{n-1})
(a1,a2,……an)=(a1,a2−a1,a3−a2,……an−an−1)
g
c
d
(
a
[
l
]
,
a
[
l
+
1
]
,
…
…
a
[
r
]
)
=
g
c
d
(
a
[
l
]
,
b
[
l
+
1
]
,
b
[
l
+
2
]
,
b
[
r
]
)
gcd(a[l],a[l+1],……a[r])=gcd(a[l],b[l+1],b[l+2],b[r])
gcd(a[l],a[l+1],……a[r])=gcd(a[l],b[l+1],b[l+2],b[r])
所以我们输入a[n]后弄出差分序列b[n]
问题变成了对于差分序列b[n]进行如下操作
操作1:
b
[
l
]
+
d
,
b
[
r
+
1
]
−
d
b[l]+d,b[r+1]-d
b[l]+d,b[r+1]−d
操作2:求
[
l
,
r
]
[l,r]
[l,r]最大公约数
g
c
d
(
a
[
l
]
,
g
c
d
(
b
[
l
+
1
]
,
…
…
b
[
r
]
)
)
gcd(a[l],gcd(b[l+1],……b[r]))
gcd(a[l],gcd(b[l+1],……b[r]))
可以发现a[l]也就是b数组的前缀和所以我们还要维护一个sum
struct Node{
int l,r;
LL g;//gcd
LL sum;//sum
}
区间查询的答案就是 a n s = a b s ( g c d ( s u m [ 1 , l ] , g c d [ l + 1 , r ] ) ) ans=abs(gcd(sum_{[1,l]},gcd_{[l+1,r]})) ans=abs(gcd(sum[1,l],gcd[l+1,r]))
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+10;
int n,m;
LL a[N],b[N];
struct Node{
int l,r;
LL g;
LL sum;
}tr[N*4];
/*维护更新信息*/
void pushup(Node &u,Node &l,Node &r){
u.g=__gcd(l.g,r.g);
u.sum=l.sum+r.sum;
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l==r)tr[u]={l,r,b[r],b[r]};//叶子节点
else{
tr[u]={l,r};//别忘记
int mid=(l+r)>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);//递归左右区间
pushup(u);
}
}
void modify(int u,int x,LL v){
if(tr[u].l==x&&tr[u].r==x)tr[u]={x,x,tr[u].sum+v,tr[u].sum+v};//叶子节点
else{
int mid=(tr[u].l+tr[u].r)>>1;
if(x<=mid)modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
/*因为查询也有很多信息要合并所以直接传结构体方便些*/
Node query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r)return tr[u];//区间被包含
else{
int mid=(tr[u].l+tr[u].r)>>1;
if(r<=mid)return query(u<<1,l,r);
else if(l>mid)return query(u<<1|1,l,r);
else{
auto left=query(u<<1,l,r);
auto right=query(u<<1|1,l,r);
Node res;
pushup(res,left,right);//合并左右子区间答案
return res;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)b[i]=a[i]-a[i-1];
build(1,1,n);
int l,r;LL d;
char op[2];
while(m--){
scanf("%s%d%d",op,&l,&r);
if(*op=='Q'){
auto left=query(1,1,l);
Node right({0,0,0,0});
if(l+1<=r)right=query(1,l+1,r);
printf("%lld\\n",abs(gcd(left.sum,right.g)));
}
else{
scanf("%lld",&d);
modify(1,l,d);
if(r+1<=n)modify(1,r+1,-d);
}
}
return 0;
}
以上是关于[线段树 差分 区间转单点]区间最大公约数的主要内容,如果未能解决你的问题,请参考以下文章