给定一个长度为 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
分析
- 一看是区间修改,可能就要懒标记,但是这题是区间加上一个数,那么可以利用差分转换成单点修改问题;那么线段树维护a的差分数组;区间查询的是最大公约数,然后最大公约数有一个性质:gcd(a1,a2,a3…,an)=gcd(a1,a2-a1,a3-a2…,an-an-1);
- 然后利用这个性质,可以把求区间[L,R]的最大公约数:gcd(a[L],gcd(a[L+1],a[L+2],…a[R])),转化为gcd(a[L],gcd(b[L+1],b[L+2],…b[R])),其中**a[L]**可以通过差分数组求前缀和求得(b[1]+…+b[L])(不能直接用输入时的a[L],因为有修改操作,原数组以及物是人非了),通过对线段树的[1,L]求前缀和即可;
- 不能直接query [L,R]的node,直接输出d,因为线段树维护的是差分数组的gcd值,所以我们要通过上面的转换,分两步求;
- 需要注意越界问题和abs问题:主函数,在查询、修改时,涉及到L+1、r+1的时候都要特判一下,具体见代码;还要注意输出答案的时候加一个绝对值,因为区间修改可能造成某个结点的d为负数,把结果加上绝对值即可;可参考:AcWing 246. 区间最大公约数(线段树 差分 辗转相除)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 500010;
struct node {
int l, r;
LL sum, d; //d为最大公约数
};
int n, m;
LL a[N];
node t[N * 4];//维护a的差分
LL gcd(LL a, LL b) {
return b == 0 ? 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(t[u], t[u << 1], t[u << 1 | 1]);
}
void build(int u, int l, int r) {
t[u] = {l, r};
//找到叶子
if (l == r) {
//本题有用于建立线段树的初始数组a
t[u].sum = a[l] - a[l - 1];//差分
t[u].d = a[l] - a[l - 1];
return;
}
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 (t[u].l == x && t[u].r == x) {
//t[u].sum=t[u].d=v; 不能这样写,因为区间加一个v,不是修改成v
t[u].sum = t[u].sum + v;
t[u].d = t[u].d + v;
return;
}
//找叶子
int mid = t[u].l + t[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) {
//1. 完全包含
if (l <= t[u].l && r >= t[u].r)
return t[u];
int mid = t[u].l + t[u].r >> 1;
//2.在左边
if (r <= mid)
return query(u << 1, l, r);
//3.在右边
else if (l >= mid + 1)
return query(u << 1 | 1, l, r);
//4.两边都有
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() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
build(1, 1, n);
while (m--) {
char op;
int l, r;
LL d;
cin >> op >> l >> r;
if (op == 'Q') {
//维护[1,L]的结点
node left = query(1, 1, l);
//维护[L+1,R]的结点
node right = {0, 0, 0, 0};
if (l + 1 <= n)//当l==r==n时候,l+1就越界了
right = query(1, l + 1, r);
cout << abs(gcd(left.sum, right.d)) << endl;
//线段树维护的是b的区间最大公约数,要把所求的a,转换为和b相关的
//转化为gcd(a[L],gcd(b[L+1],b[L+2],....b[R])),也就是上面所输出的
//cout << abs(query(1, l, r).d) << endl;
} else {
cin >> d;
//区间+d: b[l]+d ,b[r+1]-d
modify(1, l, d);
if (r + 1 <= n)//特判
modify(1, r + 1, -d);
}
}
return 0;
}