题目描述
给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、“Q l r”,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行表示M条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
N≤500000,M≤100000
输入样例:
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
题解:线段树用于在区间上进行信息统计,这题求区间GCD显然可以用线段树来做。同时题目还需要区间更新。
我们知道gcd(x,y) = gxd (x,y-x)。它可以进一步扩展到三个数的情况:gcd(x,y,z) = gcd(x,y-x,z-y)。因此我们可以构造一个长度为N的新数列B,其中B[i] = A[i] - A][i-1]。数列B为A的差分序列。用线段树维护序列B的区间GCD就相当于维护A的区间GCD。这样一来询问[l,r]的GCD就相当于求gcd(A[l],B[l+1,r]的gcd),更新[l,r]只需在B的线段树上更新l和r+1这两个点即可(注意r+1<=n),同时还要修改A中的值,这时我们可以用支持“区间修改、单点查询”的树状数组来维护。
代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 5e5 + 10;
struct node{
ll l,r,gcd;
}T[N<<2];
ll a[N],b[N],c[N];
void up(int rt) {
T[rt].gcd = __gcd(T[rt<<1].gcd,T[rt<<1|1].gcd);
}
void build(int rt,ll l,ll r) {
T[rt].l = l,T[rt].r = r;
if (l == r) {
T[rt].gcd = b[l];
return;
}
ll mid = (l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
up(rt);
}
void update(int rt,ll x,ll val) {
if (T[rt].l == T[rt].r && T[rt].l == x) {
T[rt].gcd += val;
return;
}
ll mid = (T[rt].l + T[rt].r)>>1;
if (x <= mid) update(rt<<1,x,val);
else update(rt<<1|1,x,val);
up(rt);
}
ll ask(int rt,ll l,ll r){
if (l <= T[rt].l && r >= T[rt].r) return abs(T[rt].gcd);
ll mid = (T[rt].l+T[rt].r)>>1;
ll ans1 = 0,ans2 = 0;
if (l<=mid) ans1 = ask(rt<<1,l,r);
if (r>mid) ans2 = ask(rt<<1|1,l,r);
return abs(__gcd(ans1,ans2));
}
ll lowbit(ll x) { return x&(-x); }
void add(ll x,ll val) {
for (;x<N;x+=lowbit(x)) c[x]+=val;
}
ll query(ll x) {
ll ans = 0;
for (;x>0;x-=lowbit(x)) ans += c[x];
return ans;
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; i++) {
scanf("%lld",&a[i]);
b[i] = a[i] - a[i-1];
}
build(1,1,n);
while (m--) {
char op[5];
ll l,r,x;
scanf("%s%lld%lld",op,&l,&r);
if (op[0] == 'Q') printf("%lld\n",__gcd(a[l]+query(l),ask(1,l+1,r)));
else {
scanf("%lld",&x);
update(1,l,x);
if (r<n) update(1,r+1,-x);
add(l,x);
add(r+1,-x);
}
}
return 0;
}
