题目:https://www.acwing.com/problem/content/247/
题意:给定一个长度为 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≤500000,M≤100000, 1≤A[i]≤1018, |d|≤1018,不会超longlong
题解:最大公约数支持交换律和结合律,所以我可以用两个子区间的gcd再取个gcd就是我这整个区间的gcd,所以如果只有第2个操作的话,直接用线段树来维护gcd就可以了。
最大公约数还有一个性质gcd(x,y,z)=gcd(x,y-x,z-y)=k , 但gcd(x,y,z)和gcd(x+d,y+d,z+d)没有关系。根据这个性质,我们可以用线段树来维护差分数组的最大公约数,这样区间增加一个数,就可以利用差分数组变成单点修改了,然后线段树中再记录一个区间和,到时候1-l这个区间的区间和就是现在的a[l]了,因为差分数组的前缀和就是我没有差分之前的数,维护好这两个数之后,求l-r的最大公约数,就可以用gcd( a[l] , gcd( b[l+1] ~ b[r] ) )来求取结果。
#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
typedef unsigned long long LL;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
inline int read() {
char ch = getchar();
int s = 0, w = 1;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0', ch = getchar();
}
return s * w;
}
const int N = 1e6 + 9;
int n, m;
ll w[N];
struct node {
int l, r;
ll sum, d; //sum存区间和,到时候询问1-l之间的sum,就是我现在的w[l]
//d存区间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; //我这一段区间sum和,就是两个字节点的
u.d = gcd(l.d, r.d); //我这一段的最大公约数,就是两端区间分别的最大公约数再取个gcd
}
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 = w[r] - w[r - 1]; //求出差分
tr[u] = {l, r, b, b}; //存差分值
return;
}
//往下找,并pushup求出非叶子节点的数据
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 v) {
if (tr[u].l == x && tr[u].r == x) {
ll b = tr[u].sum + v;
tr[u] = {x, x, b, b};
} else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid)
modify(u << 1, x, v);
else
modify(u << 1 | 1, x, v);
pushup(u);
}
}
//和上一道题一样,查询的时候因为是跨的不是线段树本来分割好的父区间与子区间
//所以自己要创造一个父区间res,让查询到的符合if的情况的区间,来更新我这个父区间
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 {
auto left = query(u << 1, l, r);
auto right = query(u << 1 | 1, l, r);
node res;
pushup(res, left, right);
return res;
}
}
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &w[i]);
build(1, 1, n);
int l, r;
ll d;
char op[2];
while (m--) {
scanf("%s%d%d", op, &l, &r);
if (*op == 'Q') {
node left = query(1, 1, l); //询问1-l,主要是为了求left.sum,这个sum就是现在的w[i]
node right = {0, 0, 0, 0}; //l+1>r的情况
if (l + 1 <= r) right = query(1, l + 1, r); //求出l+1到r之间的差分的最大公约数
printf("%lld\n", abs(gcd(left.sum, right.d)));
} else {
scanf("%lld", &d);
modify(1, l, d); //线段树维护的是差分数组,所以变成单点修改
if (r + 1 <= n) modify(1, r + 1, -d);
}
}
return 0;
}