题意
- 给我们一个长度为 n 数组 A,m 次操作,两种操作如下:
- 把一个区间 [l, r] 中的数都加上 d
- 询问区间 [l, r] 中所有数的最大公因数是多少?
思路
-
由于区间 gcd 是满足结合行的,可以比较容易的维护区间 gcd ,可以用 rmq,线段树等,
-
但是这题又有区间修改操作,当一个区间被加上一个数之后,好像已经无法维护区间 gcd 了,因此我们需要曲线救国间接求解答案。
-
我们由辗转相除法可以知道:gcd (a,b)=gcd (a,b-a), 那么同理:(a+d, c+d, b+d)= (a+d, c, b). 那么当我们对区间 [l,r] 加 d 之后,
-
从上面的图片中式子我们可以看出,要想求左边式子的 gcd,我们可以通过求下面两个数的 gcd 式:
-
我们可以通相邻两个数的差值,即差分建立树状数组维护区间操作,通过树状数组来求 a l + d a_l+d al+d, 然后在通过建立一颗线段树维护静态区间的 gcd, 即求第上图中的第二个数。
-
分别维护好了两个数就可以求解答案了。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ls (k << 1)
#define rs (k << 1 | 1)
const int N = 500005;
int n, m;
ll a[N];
struct Tree
{
int l, r;
ll sum, d;
} tr[N * 4];
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a; }
void push(Tree & a, Tree b, Tree c)
{
a.sum = b.sum + c.sum;
a.d = gcd(b.d, c.d);
}
void push_up(int k)
{
push(tr[k], tr[ls], tr[rs]);
}
void build(int k, int l, int r)
{
tr[k] = {
l, r };
if (l == r)
{
tr[k] = {
l, r, a[l] - a[l - 1], a[l] - a[l - 1] };
return;
}
int md = (l + r) >> 1;
build(ls, l, md);
build(rs, md + 1, r);
push_up(k);
}
void modify(int k, int x, ll y)
{
if (tr[k].l == tr[k].r)
{
ll z = tr[k].sum + y;
tr[k] = {
x, x, z, z };
return;
}
int md = (tr[k].l + tr[k].r) >> 1;
if (x <= md) modify(ls, x, y);
else modify(rs, x, y);
push_up(k);
}
Tree query(int k, int l, int r)
{
if (l > r) return Tree{
0 };
if (tr[k].l >= l && tr[k].r <= r)
{
return tr[k];
}
int md = (tr[k].l + tr[k].r) >> 1;
if (r <= md) return query(ls, l, r);
else if (l > md) return query(rs, l, r);
else
{
Tree t;
push(t, query(ls, l, r), query(rs, l, r));
return t;
}
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
build(1, 1, n);
char op[2]; ll l, r, x;
while (m --)
{
scanf("%s %lld %lld", op, &l, &r);
if (* op == 'C')
{
scanf("%lld", &x);
modify(1, l, x);
if (r + 1 <= n) modify(1, r + 1, -x);
}
else
{
ll pre = query(1, 1, l).sum;
ll suf = query(1, l + 1, r).d;
printf("%lld\n", abs(gcd(pre, suf)));
}
}
return 0;
}