题目描述
给定一个长度为 N N N 的数列 A A A,以及 M M M条指令,每条指令可能是以下两种之一:
C l r d
,表示把 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],…,A[r] 都加上 d d d。Q l r
,表示询问 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数 N , M N,M N,M。
第二行 N N N 个整数 A [ i ] A[i] A[i]。
接下来 M M M 行表示 M M M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。每个答案占一行。
数据范围
N ≤ 500000 , M ≤ 100000 N≤500000,M≤100000 N≤500000,M≤100000,
1 ≤ A [ i ] ≤ 1 0 18 1≤A[i]≤10^{18} 1≤A[i]≤1018,
∣ d ∣ ≤ 1 0 18 |d|≤10^{18} ∣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
题目分析
根据《九章算术》中的“更相减损术”,我们知道 g c d ( a , b ) = g c d ( a , a − b ) gcd(a,b)=gcd(a,a-b) gcd(a,b)=gcd(a,a−b)。再根据数学归纳法,我们可以得到更一般的结论: g c d ( a 1 , a 2 , … , a n ) = g c d ( a 1 , a 2 − a 1 , … , a n − a n − 1 ) gcd(a_1,a_2,…,a_n)=gcd(a_1,a_2-a_1,…,a_n-a_{n-1}) gcd(a1,a2,…,an)=gcd(a1,a2−a1,…,an−an−1)。
不难发现,这里的 a 2 − a 1 , … , a n − a n − 1 a_2-a_1,…,a_n-a_{n-1} a2−a1,…,an−an−1 是差分的形式。因此,我们可以构造原数组的差分数组 b b b,用线段树维护数组 b b b 的最大公约数。这样一来,区间修改变成了两次单点修改,区间查询也变成了 g c d ( a [ l ] , q u e r y ( 1 , l + 1 , r ) ) gcd(a[l],query(1,l+1,r)) gcd(a[l],query(1,l+1,r))。
对于差分数组的维护,既可用树状数组,也可线段树直接维护。这里直接用线段树维护区间和从而维护差分。
代码:
#include <iostream>
#include <cmath>
using namespace std;
const int N = 500010;
typedef long long ll;
ll a[N], n, m;
struct node{
int l, r;
ll sum, g; //维护区间和与区间gcd
}tr[N * 4];
ll gcd(ll a, ll b){
return b ? gcd(b, a % b) : a;
}
void pushup(node &u, node &l, node &r){
u.sum = l.sum + r.sum;
u.g = gcd(l.g, r.g);
}
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){
ll b = a[r] - a[r - 1];
tr[u] = {
l, r, b, b};
}
else{
tr[u].l = l, tr[u].r = 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 d){
if (tr[u].l == x && tr[u].r == x){
ll b = tr[u].sum + d;
tr[u] = {
x, x, b, b};
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, d);
else modify(u << 1 | 1, x, d);
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{
node left = query(u << 1, l, r), right = query(u << 1 | 1, l, r);
node res;
pushup(res, left, right);
return res;
}
}
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> a[i];
build(1, 1, n);
while (m --){
char opt[2];
int l, r;
ll d;
cin >> opt >> l >> r;
if (* opt == 'C'){
cin >> d;
modify(1, l, d);
if (r + 1 <= n) modify(1, r + 1, -d);
}
else{
node left = query(1, 1, l);
node right;
if (l + 1 <= r) right = query(1, l + 1, r);
cout << abs(gcd(left.sum, right.g)) << endl;
}
}
return 0;
}