题目地址:
https://www.acwing.com/problem/content/247/
给定一个长度为
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
维护和询问区间信息的问题,想到用线段树。接下来考虑每个树节点要存什么信息。首先要存区间数的最大公约数,并且最大公约数确实能由左右子区间的最大公约数算出来。接下来要考虑如何应付将区间每个数都加上 d d d。如果要把这一步操作也变为单点修改的话,能大大简化问题的复杂性,而差分数组能很好的解决这个问题。并且,容易验证, gcd ( a 1 , a 2 , . . . , a k ) = gcd ( a 1 , a 2 − a 1 , . . . , a k − a k − 1 ) \gcd (a_1, a_2, ..., a_k)=\gcd(a_1, a_2-a_1,...,a_k-a_{k-1}) gcd(a1,a2,...,ak)=gcd(a1,a2−a1,...,ak−ak−1)(很容易验证两边的数的公约数集合是一模一样的,那么最大公约数当然也是一样的),所以每次求区间 [ l , r ] [l,r] [l,r]最大公约数的时候,我们只需要求出 a l a_l al,再求一下 gcd ( a l + 1 , . . . , a r ) \gcd(a_{l+1},...,a_r) gcd(al+1,...,ar)即可,前者可以由差分数组的前缀和求出,后者则直接询问两个子区间即可。所以我们只需要构造一个维护 A [ i ] A[i] A[i]的差分数组 D [ i ] = A [ i ] − A [ i − 1 ] , D [ 0 ] = 0 D[i]=A[i]-A[i-1],D[0]=0 D[i]=A[i]−A[i−1],D[0]=0的线段树即可,树节点存区间和与区间最大公约数,修改的时候,如果要让 [ l , r ] [l,r] [l,r]的所有元素都加 d d d,那么相当于对差分数组做两次单点修改,即让 D [ l ] D[l] D[l]增加 d d d并且让 D [ r + 1 ] D[r+1] D[r+1]增加 − d -d −d(当然如果 r + 1 > n r+1>n r+1>n那就不用做这步了);询问的时候,则先询问 [ 1 , l ] [1,l] [1,l]的区间和,再询问 [ l + 1 , r ] [l+1,r] [l+1,r]的最大公约数,两个数再求一下最大公约数即得 [ l , r ] [l,r] [l,r]的最大公约数,这里依然要注意特殊情况 l + 1 > r l+1>r l+1>r,此时最大公约数设为 0 0 0即可。代码如下:
#include <iostream>
using namespace std;
const int N = 500010;
int n, m;
// a存输入的序列
long a[N];
// l和r存树节点维护的差分数组的区间左右端点,sum存区间和,d存区间最大公约数
struct Node {
int l, r;
long sum, d;
} tr[4 * N];
long gcd(long a, long b) {
return !b ? a : gcd(b, a % b);
}
void pushup(Node &u, Node &l, Node &r) {
u.sum = l.sum + r.sum;
u.d = gcd(l.d, r.d);
}
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) {
long b = a[r] - a[r - 1];
tr[u] = {l, r, b, b};
} else {
tr[u] = {l, r};
int m = l + (r - l >> 1);
build(u << 1, l, m), build(u << 1 | 1, m + 1, r);
pushup(u);
}
}
// 让差分数组的下标x的位置增加v
void modify(int u, int x, long v) {
if (tr[u].l == x && tr[u].r == x) {
long b = tr[u].sum + v;
tr[u] = {x, x, b, b};
} else {
int m = tr[u].l + (tr[u].r - tr[u].l >> 1);
if (x <= m) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
Node query(int u, int l, int r) {
// 如果当前询问的区间无效,那么就返回个0,因为0与任何数的最大公约数都是对方
if (l > r) return {0};
if (l <= tr[u].l && tr[u].r <= r) return tr[u];
else {
int m = tr[u].l + (tr[u].r - tr[u].l >> 1);
if (r <= m) return query(u << 1, l, r);
else if (l > m) return query(u << 1 | 1, l, r);
else {
auto left = query(u << 1, l, r), 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("%ld", &a[i]);
build(1, 1, n);
int l, r;
long d;
char op[2];
while (m--) {
scanf("%s%d%d", op, &l, &r);
if (op[0] == 'Q') {
auto left = query(1, 1, l), right = query(1, l + 1, r);
cout << abs(gcd(left.sum, right.d)) << endl;
} else {
scanf("%ld", &d);
modify(1, l, d);
if (r + 1 <= n) modify(1, r + 1, -d);
}
}
return 0;
}
预处理时间复杂度 O ( N ) O(N) O(N),每次操作时间复杂度 O ( log N ) O(\log N) O(logN),空间 O ( N ) O(N) O(N)。