区间最大公约数【线段树+树状数组】
CH4302
、ACwing246
题目:
给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d
,表示把 A[l],A[l+1],…,A[r] 都加上 d。Q l r
,表示询问 A[l],A[l+1],…,A[r]的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。
输入:
第一行两个整数 N,M。
第二行 N 个整数 A[i]。
接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。
输出:
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围:
N≤500000,M≤100000,
1≤A[i]≤1018,
|d|≤1018
样例输入:
5 5
1 3 5 7 9
Q 1 5
C 1 5 1
Q 1 5
C 3 3 6
Q 2 4
样例输出:
1
2
4
思路:
性质:
1.gcd(a1,a2) = gcd(a1,a2-a1),归纳:
2.gcd(a1,a2,a3,...) = gcd(a1,a2-a1,a3-a2...)
因为涉及到了区间修改,我们可以利用差分来做这个题,然后每个点的值使用树状数组来维护前缀和,
而区间[l,r]修改的话,就在差分数组d[l]加上一个数,同时在d[r+1]减去一个数。
同时用一个线段树来维护区间最大公约数,每次修改操作只对d[l]和d[r+1]产生了影响,因此线段树也只需要修改两个点。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+5;
int a[N],d[N + 5],tree1[N + 5]; //分别存储初始数组、差分数组、树状数组
struct Node{
int l,r; //存储区间
int m_gcd; //区间最大gcd
} tree2[N << 2]; //存储线段树
int n,m;
int gcd(int x,int y){ //返回x,y的最大公约数
return y == 0 ? x : gcd(y,x % y);
}
void build(int p,int l,int r){ //建立结点p,区间为[l,r]
tree2[p].l = l,tree2[p].r = r;
if(l == r){
tree2[p].m_gcd = d[l]; //区间最大公约数就是本身
return;
}
int lc = p<<1,rc = p<<1|1,mid = l+r>>1;
build(lc,l,mid); //建立左孩子结点
build(rc,mid + 1,r); //建立右孩子结点
tree2[p].m_gcd = gcd(tree2[lc].m_gcd,tree2[rc].m_gcd); //该结点的最大区间公约数是左孩子、右孩子的gcd
}
void update2(int p,int x,int k){ //将d[x]加上k
if(tree2[p].l == x && tree2[p].r == x){
tree2[p].m_gcd += k;
return;
}
int lc = p<<1,rc = p<<1|1,mid = tree2[p].l+tree2[p].r>>1;
if(x<=mid)
update2(lc,x,k);
else
update2(rc,x,k);
tree2[p].m_gcd = gcd(tree2[lc].m_gcd,tree2[rc].m_gcd);
}
int get_gcd(int p,int l,int r){ //查找区间最大公约数
if(tree2[p].l == l && tree2[p].r == r)
return tree2[p].m_gcd;
int lc = p<<1,rc = p<<1|1,mid = tree2[p].l+tree2[p].r>>1;
if(r <= mid)
return get_gcd(lc,l,r);
else if(l > mid)
return get_gcd(rc,l,r);
else
return gcd((get_gcd(lc,l,mid)),(get_gcd(rc,mid + 1,r)));
}
void update1(int x,int y){ //用于更新树状数组d[x] += y
for(;x <= n;x += x & -x)
tree1[x] += y;
}
int get_sum(int x){ //用于查找前缀和d[1]+...+d[x]
int ans = 0;
for(;x;x -= x & -x)
ans += tree1[x];
return ans;
}
signed main(){
cin>>n>>m;
for(int i = 1;i <= n;++i){
cin>>a[i];
d[i] = a[i] - a[i-1]; //差分
update1(i,d[i]); //建立树状数组
}
build(1,1,n); //建立线段树
char op;int l,r,k;
while(m--){
cin>>op;
if(op == 'C'){
cin>>l>>r>>k;
update1(l,k);update1(r+1,-k); //更新树状数组
update2(1,l,k); //更新线段树
if(r < n) //如果r+1超过了n,那么就不用更新线段树了
update2(1,r+1,-k);
}
else{
cin>>l>>r;
if(l == r)
cout<<abs(get_sum(l))<<"\n";
else
cout<<abs(gcd(get_sum(l),get_gcd(1,l + 1,r)))<<"\n";
}
}
return 0;
}