AcWing 246. 区间最大公约数
题目传送门
一级标题提醒:此题细节很多
1. \(gcd(a,b)=gcd(a,b?a)\)
在\(lyd\)的书里提到的这个性质叫更相减损术,可以推广到多个数的情况。
更相减损术其实是欧几里得算法的一个特例。即\(gcd(a?nb,b)=gcd(a,b)\)。
\((a,b,c)=((a,b),(b,c))=((a,b?a),(b,c?b))=(a,b?a,b,c?b)\)
由于\((b?a,b)=(a,b?a)\)所以\((a,b,c)=(a,b?a,c?b)\)
有了这个式子说明可以通过维护序列的差分来达到求\(gcd\)同样的效果。
比如求\((a,b,c)\)只需要知道现在\(a\)的值,然后知道\((b?a,c?b)\)的\(gcd\),再求一个公约数就行了。
-
差分就可以把区间加减变成单点加减。可以用没有\(lazy\)的线段树来做。
-
再维护一个差分,做成树状数组或者线段树,用来维护每个数的值。
2. \(gcd(a,b)=gcd(a,?b)\)
在数值加减的过程中可能会产生负数,而约定\(gcd\)是没有负数的,所以需要用这个式子来搞定负数。
具体来说,就是在每次查询或者更新的时候,如果遇到了负数,就把它取反。
注意:只能对结果取反而不能直接把线段树的负数叶子节点取反。因为直接把叶子取反会对今后的加减操作造成影响。
差分操作
差分操作是\(a[x]+d,a[y+1]-d;\)这个时候就可能出现\(y+1\)越界的情况。需要及时特判掉。
实现代码:
#include
using namespace std;
typedef long long LL;
const int N = 500010;
int n, m;
LL w[N];
struct Node {
int l, r;
LL sum; //区间总和
LL d; //区间内的最大公约数
} tr[N << 2];
//求最大公约数
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.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) {
LL b = w[r] - w[r - 1]; //更相减损数,所以按原数组差分构建,yxc大佬很良心修改了试题,添加了1e18的数据范围说明
tr[u] = {l, r, b, b}; //当是叶子节点时,区间和就是自己,区间最大公约数也是自己
return;
}
tr[u].l = l, tr[u].r = r; //不加这句就和yxc一样的下场~
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
//子节点变更需要更新父节点需要更新父节点的总和、最大公约数
pushup(u);
}
//以u为根的子树中,修改位置为x的节点,值为+v
void modify(int u, int x, LL v) {
if (tr[u].l == x && tr[u].r == x) { //找到叶子节点
LL b = tr[u].sum + v; //+v
tr[u].sum = tr[u].d = b; //修改区间和与最大公约数
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) //在左侧
modify(u << 1, x, v); //让左儿子处理
else //在右侧
modify(u << 1 | 1, x, v); //让右儿子处理
// u的子节点数据变更,需要从u开始向上更新父节点信息
pushup(u);
}
//查询
Node query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
if (l > mid) return query(u << 1 | 1, l, r);
Node left = query(u << 1, l, r);
Node 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 >> w[i];
//因为差分的r+1可能越界,这里在建立线段树时就多创建一个位置就OK
build(1, 1, n + 1);
int l, r;
LL d;
char op;
while (m--) {
cin >> op >> l >> r;
if (op == 'Q') {
auto left = query(1, 1, l);
Node right({0, 0, 0, 0});
if (l + 1 <= r) right = query(1, l + 1, r);
printf("%lld\n", abs(gcd(left.sum, right.d)));
} else {
cin >> d;
modify(1, l, d), modify(1, r + 1, -d);
}
}
return 0;
}